为什么这个WCF代理代码有效?

时间:2014-06-25 15:41:08

标签: c# .net wcf idisposable

在应用程序中进行调试时,我发现了以下代码。这显然是错的,但由于一些奇怪的原因,它起作用,我似乎无法理解为什么。在我看来,代理将在创建后立即处理,但调用此代理上的方法可以正常连接到WCF服务。

任何人都可以解释为什么这段代码不会爆炸吗?

private static IMyService _proxy = null;
private static IMyService Proxy
{
  get
  {
    if (_proxy == null)
    {
      using (_proxy as IDisposable)
      {
        ChannelFactory<IMyService> factory = 
             new ChannelFactory<IMyService>("MyService");
        _proxy = factory.CreateChannel();
      }
    }
    return _proxy;
  }
}

3 个答案:

答案 0 :(得分:6)

这真的很有趣!。

很明显,在using()语句中使用现有变量和在块中重新分配原始文件的结合,会导致根据下面的IL,传递给Dispose()而不是现有_proxy的新参考。

事实上,LinqPad发出了warning这个效果:

  

可能不正确地赋值给本地'变量',它是using语句或lock语句的参数。处理呼叫或解锁将发生在本地的原始值上。

但是,这种行为似乎需要转换为as IDisposablestatic似乎也没有效果。

在LINQPad中将以下代码反汇编为IL:

IMyService _proxy = null;
if (_proxy == null)
{
   using (_proxy)
   {
      _proxy = new SomeService();
   }
}

产量

IL_0001:  ldnull      
IL_0002:  stloc.0     // _proxy
IL_0003:  ldloc.0     // _proxy
IL_0004:  ldnull      
IL_0005:  ceq         
IL_0007:  ldc.i4.0    
IL_0008:  ceq         
IL_000A:  stloc.1     // CS$4$0000
IL_000B:  ldloc.1     // CS$4$0000
IL_000C:  brtrue.s    IL_002D
IL_000E:  nop         
** IL_000F:  ldloc.0     // _proxy
** IL_0010:  stloc.2     // CS$3$0001
IL_0011:  nop         
IL_0012:  newobj      UserQuery+SomeService..ctor
IL_0017:  stloc.0     // _proxy
IL_0018:  nop         
IL_0019:  leave.s     IL_002B
$IL_001B:  ldloc.2     // CS$3$0001
$IL_001C:  ldnull      
$IL_001D:  ceq         
IL_001F:  stloc.1     // CS$4$0000
IL_0020:  ldloc.1     // CS$4$0000
$IL_0021:  brtrue.s    IL_002A
** IL_0023:  ldloc.2     // CS$3$0001
IL_0024:  callvirt    System.IDisposable.Dispose
IL_0029:  nop         
IL_002A:  endfinally  
IL_002B:  nop         

可以看出(通过**),创建了一个新的loc 2,并且这个引用被传递给Dispose()。但在此之前检查null($),在任何情况下绕过Dispose

将此与明确的try-finally

进行对比
 if (_proxy == null)
 {
    try
    {
       _proxy = new SomeService();
    }
    finally
    {
       _proxy.Dispose();
    }
 }

IL_0001:  ldnull      
IL_0002:  stloc.0     // _proxy
IL_0003:  ldloc.0     // _proxy
IL_0004:  ldnull      
IL_0005:  ceq         
IL_0007:  ldc.i4.0    
IL_0008:  ceq         
IL_000A:  stloc.1     // CS$4$0000
IL_000B:  ldloc.1     // CS$4$0000
IL_000C:  brtrue.s    IL_0025
IL_000E:  nop         
IL_000F:  nop         
IL_0010:  newobj      UserQuery+SomeService..ctor
IL_0015:  stloc.0     // _proxy
IL_0016:  nop         
IL_0017:  leave.s     IL_0023
IL_0019:  nop         
** IL_001A:  ldloc.0     // _proxy
IL_001B:  callvirt    System.IDisposable.Dispose
IL_0020:  nop         
IL_0021:  nop         
IL_0022:  endfinally  
IL_0023:  nop      

可以清楚地看到,"original" _proxy是已处置的using(),未在_proxy中进行额外的空检查,确认the docs

不用说这段代码是邪恶的:

  • Disposed永远不会是using,即_proxy是多余的。
  • It relies on passing null to using没有失败。
  • 由于using已定义之前 using (var _proxy = ...),因此可以在块中重新分配,而不像变量declared IN the using语句 - 例如read-only将是_proxy,并且尝试改变这样的变量会导致编译时错误。
  • 除了静态单例通道的优点之外,private static readonly Lazy<IMyService>(() => ...)的延迟初始化也不是线程安全的(例如没有双重检查锁定),并且可以使用{{1}}和简单的getter进行简化

答案 1 :(得分:2)

该代码的作用是因为:

using (_proxy as IDisposable)

表达式_proxy as IDisposable不是_proxy,这解释了为什么_proxy可以在using块内分配,而不会让C#编译器以任何方式抱怨。

此外,即使_proxy随后设置为非null一次性实例,原始值为&#34;看到&#34; using块(_proxy as IDisposable)仍然是null(此上下文中没有涉及引用)。

因此,没有任何处理,创建和缓存新代理,并且代码成功。

我仍然想知道为什么要编写这样的using声明的潜在原因。

答案 2 :(得分:2)

好吧,显然你可以将null作为使用范围的输入:

using (null)
{
    Console.WriteLine("hamster");
}

发生的情况是_proxy为空,因此_proxy as IDisposable也为空,这使得使用范围变为冗余,不会调用(或尝试)Dispose。代码等同于:

if (_proxy == null)
{
    ChannelFactory<IBasketService> factory =
            new ChannelFactory<IBasketService>("MyService");
    _proxy = factory.CreateChannel();
}
return _proxy;

在范围内分配_proxy并不重要,Dispose将被&#34;调用&#34;原始复制值(为空)。


这是一个简单的代码片段,可以重现这个问题。 &#34; Disposed&#34;消息将写入控制台。

internal class Program
{
    private static DisposableScope _proxy = null;
    private static void Main(string[] args)
    {
        using (_proxy)
        {
            _proxy = new DisposableScope();
        }
    }
}

public class DisposableScope : IDisposable
{
    public void Dispose()
    {
        Console.WriteLine("Disposed");
    }
}