为什么这个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;
}
}
这真的很有趣!
很明显,在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
语句的潜在原因,然而。