为什么这个WCF代理代码工作?

本文关键字:代码 工作 代理 WCF 为什么 | 更新日期: 2023-09-27 18:10:25

在一个应用程序中调试时,我发现了以下代码。这显然是错误的,但出于某种奇怪的原因,它还是行得通,我似乎不明白为什么。在我看来,代理一旦创建就会被处理掉,但是在这个代理上调用方法可以正确地连接到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;
  }
}

为什么这个WCF代理代码工作?

这真的很有趣!

很明显,在using()语句中使用现有的变量和在块内重新赋值的组合,导致一个新的引用被传递给Dispose(),而不是现有的_proxy,如下面的IL所示。

事实上,LinqPad会发出这样的警告:

可能不正确地赋值给本地变量,该变量是using或lock语句的参数。Dispose调用或解锁将发生在local的原始值上。

然而,这种行为似乎不需要强制转换到as IDisposable, static似乎也没有任何影响。

在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         

可以看到(通过**'s),创建了一个新的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()中进行额外的null检查,确认文档。

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

  • _proxy永远不会是Disposed,即using是冗余的。
  • 它依赖于传递null给using不失败。
  • 因为_proxy是在 using之前定义的,所以它可以在块内重新分配,不像在using语句中声明的变量-例如using (var _proxy = ...)将是read-only,并且试图改变这样的变量无论如何都会导致编译时错误。除了静态单例通道的优点之外,_proxy的延迟初始化也不是线程安全的(例如,没有双重检查锁),并且可以通过private static readonly Lazy<IMyService>(() => ...)和简单的getter来简化。

显然,您可以将null作为using作用域的输入:

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

发生的是_proxy是空的,因此_proxy as IDisposable也是空的,这使得using作用域是多余的,没有Dispose将被调用(或尝试)。代码相当于:

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

在作用域内,_proxy被赋值并不重要,Dispose将在原始复制的值(为null)上被"调用"。


下面是再现这个问题的一个简单代码片段。"Disposed"消息将而不是写入控制台。

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");
    }
}

由于:

using (_proxy as IDisposable)

表达式_proxy as IDisposable不是_proxy,这就解释了为什么_proxy可以在using块内赋值而不会引起c#编译器的任何抱怨。

此外,即使_proxy随后被设置为非null的一次性实例,using块(_proxy as IDisposable)"看到"的原始值仍然是null(在此上下文中不涉及引用)。

因此,没有任何东西被处理,一个新的代理被创建和缓存,并且代码成功。

我仍然想知道为什么有人想写这样一个using语句的潜在原因,然而。