Erik Funkenbusch
Erik Funkenbusch

Reputation: 93444

Why does this WCF Proxy code work?

While debugging in an app, I found the following code. It's obviously wrong, but for some strange reason it works, and I can't seem to understand why. It would seem to me that the Proxy would be disposed as soon as it was created, yet calling methods on this proxy work correctly to connect to the WCF service.

Can anyone explain why this code doesn't blow up?

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

Upvotes: 3

Views: 158

Answers (3)

StuartLC
StuartLC

Reputation: 107357

This really is interesting!.

It is apparent that combination of the use of an existing variable in the using() statement and the reassignment of the original within the block, results in a new reference which is passed to Dispose(), and not the existing _proxy, as per the IL below.

In fact, LinqPad issues a warning to this effect:

Possibly incorrect assignment to local 'variable' which is the argument to a using or lock statement. The Dispose call or unlocking will happen on the original value of the local.

However this behaviour does not appear to require a cast to as IDisposable, nor does the static seem to have any effect.

Disassembling the following code to IL in LINQPad:

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

Yields

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         

As can be seen (by the **'s), that a new loc 2 is created, and it is this reference which is passed to Dispose(). But prior to this is a check for null ($), which bypasses the Dispose in any event.

Contrast this with the explicit 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      

Where it can be clearly seen that it is the "original" _proxy which is Disposed, without the extra null check done in the using(), confirming the docs.

Needless to say this code is evil:

  • _proxy will never be Disposed, viz the using is redundant.
  • It relies on passing null to using not failing.
  • Because _proxy is defined prior to the using, it can be reassigned within the block, unlike variables declared IN the using statement - e.g. using (var _proxy = ...) would be read-only, and attempting to mutate such a variable would result in a compile time error anyway.
  • The merits of static singleton channels aside, the lazy initialization of _proxy is also not thread-safe (e.g. no double-check lock), and could be simplified with a private static readonly Lazy<IMyService>(() => ...) and a simple getter.

Upvotes: 6

Fr&#233;d&#233;ric Hamidi
Fr&#233;d&#233;ric Hamidi

Reputation: 263097

That code works because of:

using (_proxy as IDisposable)

The expression _proxy as IDisposable is not _proxy, and that explains why _proxy can be assigned to within the using block without the C# compiler complaining in any way.

In addition, even if _proxy is subsequently set to a non-null disposable instance, the original value as "seen" by the using block (_proxy as IDisposable) will still be null (there are no references involved in this context).

Therefore, nothing is disposed of, a new proxy is created and cached, and that code succeeds.

I'm still wondering about the potential reasons why one would like to write such a using statement, however.

Upvotes: 2

i3arnon
i3arnon

Reputation: 116636

Well, apparently you can have null as the input to a using scope:

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

What happens is that _proxy is null and therefore _proxy as IDisposable is also null which makes the using scope redundant, no Dispose will be called (or attempted). The code is equivalent to this:

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

It doesn't matter that _proxy is assigned to while inside the scope, Dispose would be "invoked" on the original copied value (which is null).


Here's a simple snippet that reproduces the issue. The "Disposed" message will not be written to console.

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

Upvotes: 2

Related Questions