在应用程序中进行调试时,我发现了以下代码。这显然是错的,但由于一些奇怪的原因,它起作用,我似乎无法理解为什么。在我看来,代理将在创建后立即处理,但调用此代理上的方法可以正常连接到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;
}
}
答案 0 :(得分:6)
这真的很有趣!。
很明显,在using()
语句中使用现有变量和在块中重新分配原始文件的结合,会导致根据下面的IL,传递给Dispose()
而不是现有_proxy
的新参考。
事实上,LinqPad发出了warning这个效果:
可能不正确地赋值给本地'变量',它是using语句或lock语句的参数。处理呼叫或解锁将发生在本地的原始值上。
但是,不这种行为似乎需要转换为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
可以看出(通过**),创建了一个新的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
是多余的。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");
}
}