使用的正确范围

本文关键字:范围 | 更新日期: 2023-09-27 18:04:57

我和一位同事就using语句的适当范围发生了争执。这就是有问题的方法。

public Guid IsServerReachable()
{
  try
  {
    WhoAmIResponse whoAmI;
    using (OrganizationServiceProxy service = GetService())
      whoAmI = service.Execute(new WhoAmIRequest()) as WhoAmIResponse;
    return whoAmI.UserId;
  }
  catch { return Guid.Empty; }
}

我们中的一个声称using语句应该包括whyAmI的声明,而另一个则认为只有服务实例需要使用ified。我不知道哪一个是我的理论,但其中一个显然是错误的。哪一个

使用的正确范围

两者都是正确的。我倾向于这样写:

public Guid IsServerReachable()
{
    try
    {
        using (OrganizationServiceProxy service = GetService())
        {
            WhoAmIResponse whoAmI = service.Execute(new WhoAmIRequest()) as WhoAmIResponse;
            return whoAmI.UserId;
        }
    }
    catch { return Guid.Empty; }
}

这对whoAmI是否被处理没有任何影响——唯一被自动处理的是service

如果WhoAmIResponse也是IDisposable,则必须编写以下内容才能自动释放两者:

    using (OrganizationServiceProxy service = GetService())
    using (WhoAmIResponse whoAmI = service.Execute(new WhoAmIRequest()) as WhoAmIResponse)
        return whoAmI.UserId;

由于whoAmI的声明在这种情况下对性能/作用域没有任何影响。归根结底,whoAmI.UserId的属性访问是否也包含在using中。从IL的角度来看,两者之间唯一的函数区别是调用OrganizationalServiceProxy.Dispose方法的顺序和访问WhoAmIResponse.UserId的时间。

(编辑:我不认为如何处理try/catch以返回默认值有任何真正的问题,这似乎不是问题的一部分,所以这也被省略了(

在您发布的代码中(简化以使IL更清晰(:

public Guid IsServerReachableOutsideUsingScope()
{
    WhoAmIResponse whoAmI;
    using(var service = new Service())
        whoAmI = service.Execute();
    return whoAmI.UserId;
}

IL中的结果:

IL_0000:  newobj      UserQuery+Service..ctor
IL_0005:  stloc.1     // service
IL_0006:  ldloc.1     // service
IL_0007:  callvirt    UserQuery+Service.Execute
IL_000C:  stloc.0     // whoAmI
IL_000D:  leave.s     IL_0019
IL_000F:  ldloc.1     // service
IL_0010:  brfalse.s   IL_0018
IL_0012:  ldloc.1     // service
IL_0013:  callvirt    System.IDisposable.Dispose
IL_0018:  endfinally  
IL_0019:  ldloc.0     // whoAmI
IL_001A:  callvirt    UserQuery+WhoAmIResponse.get_UserId
IL_001F:  ret         

而声明使用块中的所有内容:

public Guid IsServerReachableWithinUsingScope()
{
    using(var service = new Service())
    {
        WhoAmIResponse whoAmI = service.Execute();
        return whoAmI.UserId;
    }
}

产生IL:

IL_0000:  newobj      UserQuery+Service..ctor
IL_0005:  stloc.0     // service
IL_0006:  ldloc.0     // service
IL_0007:  callvirt    UserQuery+Service.Execute
IL_000C:  stloc.1     // whoAmI
IL_000D:  ldloc.1     // whoAmI
IL_000E:  callvirt    UserQuery+WhoAmIResponse.get_UserId
IL_0013:  stloc.2     // CS$1$0000
IL_0014:  leave.s     IL_0020
IL_0016:  ldloc.0     // service
IL_0017:  brfalse.s   IL_001F
IL_0019:  ldloc.0     // service
IL_001A:  callvirt    System.IDisposable.Dispose
IL_001F:  endfinally  
IL_0020:  ldloc.2     // CS$1$0000
IL_0021:  ret         

如果在访问属性之前(比如在NHibernate延迟加载集合的上下文中(处理您的服务而不是很重要,那么顺序肯定很重要。如果这无关紧要,那么最大的问题应该是你和你的团队最关心什么。如果你不介意混合和匹配using调用,所以有些调用有大括号,有些没有,那么就继续使用你所拥有的。

如果访问WhoAmIResponse.UserId有副作用,可能需要考虑的是异常处理顺序如果您的服务上的Dispose调用抛出异常,那么在您的原始代码(IsServerReachableOutsideUsingScope(中,它将永远不会访问您的属性,因此永远不会执行其副作用。在第二个代码块(IsServerReachableWithinUsingScope(中,它将访问并执行使用UserId属性的副作用,然后运行抛出异常的Dispose

这些情况相当罕见(EDIT:需要注意的是,get access副作用和Dispose()抛出异常都被认为是不良做法(,我建议如果这里是,那么您应该考虑这些情况的正确性。如果这些都不是问题(没有副作用,也不关心访问/处理顺序(,那么从长远来看,使用您和您的团队认为更易于维护/可读的内容。

using语句必须包含在语句终止时应该处理的对象的声明。只要OrganizationServiceProxy实现了IDisposable,而WhoAmIResponse没有实现,您的代码就是正确的。

如果有疑问,通常将using块重写为try-filly块是有用的:

OrganizationServiceProxy service = GetService();
try {
   whoAmI = service.Execute(new WhoAmIRequest()) as WhoAmIResponse;
} finally {
    service.Dispose();
}

任何using语句的范围尽可能小是最佳实践。只要OrganizationServiceProxy在运行Dispose方法时返回的对象没有被处理,那么所指示的范围是完全可以接受的。

using (OrganizationServiceProxy service = GetService())
    whoAmI = service.Execute(new WhoAmIRequest()) as WhoAmIResponse;

将相当于:

OrganizationServiceProxy service = GetService();
try
{
    whoAmI = service.Execute(new WhoAmIRequest()) as WhoAmIResponse;
}
finally
{
    if (myRes!= null)
        // Call the object's Dispose method.
        ((IDisposable)service).Dispose();
}