Search code examples
c#lambdaconstructorrhino-mocks

Are lambdas constructors for delegate types?


I've discovered that Rhino Mocks' AssertWasCalled fails when I use lambdas as parameters to the method being asserted.

TEST :
_mockDoer.AssertWasCalled(x => x.Print(y => Console.WriteLine("hi")));

CODE INSIDE SYSTEM UNDER TEST :
_doer.Print(y => Console.WriteLine("hi")));

This has made me think of lambdas as, effectively, constructors for delegate types.

Am I missing anything important when I think of lambdas as constructors for delegate types?


Solution

  • Well, they're not really "constructors" in any of the normal uses of the word "constructor".

    They're expressions which can be converted to delegate types or expression tree types - the latter being essential when it comes to out-of-process LINQ.

    If you're really asking whether it's expected that using two "equivalent" lambda expressions can create unequal delegate instances: yes, it is. IIRC, the C# language specification even calls out that that's the case.

    However, using the same lambda expression more than once won't always create different instances:

    using System;
    
    class Test
    {
        static void Main(string[] args)
        {
            Action[] actions = new Action[2];
            for (int i = 0; i < 2; i++)
            {
                actions[i] = () => Console.WriteLine("Hello");
            }
            Console.WriteLine(actions[0] == actions[1]);
        }
    }
    

    On my box, that actually prints True - actions[0] and actions[1] have the exact same value - they refer to the same instance. Indeed, we can go further:

    using System;
    
    class Test
    {
        static void Main(string[] args)
        {
            object x = CreateAction();
            object y = CreateAction();
            Console.WriteLine(x == y);
        }
    
        static Action CreateAction()
        {
            return () => Console.WriteLine("Hello");
        }
    }
    

    Again, this prints True. It's not guaranteed to, but here the compiler has actually created a static field to cache the delegate the first time it's required - because it doesn't capture any variables etc.

    Basically this is a compiler implementation detail which you should not rely on.