【问题标题】:Correct scope of using using正确使用范围 using
【发布时间】:2013-03-20 14:12:07
【问题描述】:

我与一位同事就 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 的声明,而另一个则认为只有 service 实例需要使用化。我没有说哪个是我的理论,但一个显然是错误的。哪个?

【问题讨论】:

  • OrganizationService 将被释放(如果它实现 IDisposed),但它 WhoAmI 引用了 OrganizationService。因此,您将不会得到任何东西,因为它已经被处理掉了!您需要在 using 语句中返回。
  • 如果变量 whoAmI 的整个范围都是 using 块(缺少括号在这里有点混淆),那么在执行 return whoAmI 时它甚至不会存在。
  • 如果你包含所有三行(声明、赋值和返回),我看不出它在这里有什么意义
  • @KonradViltersten 抱歉,这是对 Dave 的回应,暗示 whoAmI 对象是被处置的对象(他的评论已被删除)。我会删除我的评论(不再有它的意义!)
  • @PieterGeerkens 正是在我决定再刷新一次之前我要输入的内容 ;) 但是,如果 WhoAmIResponse 实现了 IDisposable 并且应该被处理掉,那么 UserId 的值应复制到其类型的变量并用作返回值,WhoAmI 应在另一个 using 块中的自己的 using 语句中声明。

标签: c# scope using-statement


【解决方案1】:

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

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;

【讨论】:

  • 我有两个非常充分的理由来使用在 using 范围之外声明的 whoAmI 方法,所以除非它是完全错误的(你说不是),我会同意的。谢谢。
  • 出于兴趣:你的理由是什么?我写的东西有一些非常充分的理由:-D
  • 从 IL 的角度来看,唯一的区别是 OrganizationalServiceProxy.Dispose 被调用 之前 访问 WhoAmIResponse.UserId 属性(这是您发布的代码)或 Dispose 属性访问之后被调用(由using 块绑定的所有内容,如本答案中发布的那样)。如果这无关紧要,那么选择对您的团队来说更易读/更容易的东西。如果它确实很重要(比如使用 NHibernate 延迟加载的集合),那么答案就很清楚了,你应该使用哪种方法(只有一种方法有效!)。
【解决方案2】:

由于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         

而在 using 块中声明所有内容:

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

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

【讨论】:

    【解决方案3】:

    using 语句必须包含在语句终止时应该被释放的对象的声明。只要OrganizationServiceProxy 实现IDisposableWhoAmIResponse 不实现,您的代码就是正确的。

    如有疑问,将 using 块重写为 try-finally 块通常很有用:

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

    【讨论】:

    • 这不是编写try/finally 等效块的正确方法。如果线程进入try 块之前抛出异常,它永远不会释放服务。见:stackoverflow.com/a/2732078/1269654
    • WhoAmIResponse 是否实现IDisposable 实际上与正确性无关。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:
      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();
      }
      

      【讨论】:

      • 不完全;我很确定GetService() 分配发生在 within try 所以如果线程 before 出现异常,它进入try/finally 块,它仍然会处置。编辑:见stackoverflow.com/a/2732078/1269654
      • service的声明不应该也在try里面吗?
      • @KonradViltersten 如果这样做,您将无法在 finally 块中处理它(因为在该范围内无法访问它)在外部声明它,在内部分配它。跨度>
      猜你喜欢
      • 1970-01-01
      • 2015-09-18
      • 1970-01-01
      • 1970-01-01
      • 2013-05-30
      • 2014-10-02
      • 2012-09-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多