1

If I have a class like this:

public class SomeClass
{
    public Action<string> SomeAction { get; set; }

    public SomeClass()
    {
        SomeAction = GetSomeAnonymousMethod();
    }

    private Action<string> GetSomeAnonymousMethod()
    {
        return (text) =>
        {
            Console.WriteLine(text);
        };
    }
}

What happens when I make a new instance of SomeClass? My impression is that the constructor simply calls GetSomeAnonymousMethod(), which returns a new delegate instance (that only contains a reference to the compiler-generated backing method for the anonymous method), and assigns it to the SomeAction property.

Can someone confirm, or is something more sinister happening?

Kyle Baran
  • 1,793
  • 2
  • 15
  • 30

2 Answers2

2

Well, that's nearly all that happens. In this particular case, your lambda expression (it's not an anonymous method, to be picky) doesn't need any state, so the generated method can be static, and a delegate reference can be cached. So the "new delegate instance" part may not be correct.

So it's more like this - at least when compiled with the MS compiler:

public class SomeClass
{
    private static Action<string> cachedAction;
    public Action<string> SomeAction { get; set; }

    public SomeClass()
    {
        SomeAction = GetSomeAnonymousMethod();
    }

    private Action<string> GetSomeAnonymousMethod()
    {
        Action<string> action = cachedAction;
        if (action == null)
        {
            action = AnonymousMethodImplementation;
            cachedAction = action;
        }
        return action;
    }

    private static void AnonymousMethodImplementation(string text)
    {
        Console.WriteLine(text);
    }
}

You don't need to worry about the details of this - and it's all an implementation detail... just a bit of an optimization. But if you really want to know what's going on, that's closer to reality.

As ever, to see the details of what the compiler's doing, you can always use ildasm (or Reflector in IL mode, for example) and see the generated IL.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • There're too many people worried about such details instead of using their mind to get higher skills in the whole programming language. I believe that such low-level details shouldn't be a concern for developers. If language specification says "do it this way" this should be the concern: how it works at the language level... – Matías Fidemraizer Jun 22 '14 at 07:59
  • @MatíasFidemraizer: For 99.9% of the time, that's correct - but I wouldn't want to discourage anyone from investigating how that's implemented. – Jon Skeet Jun 22 '14 at 08:00
  • No, no. I'm agree with you. I was throwing here my opinion about the generic topic itself, but I'm not discouraging anyone from getting such valuable information! :) – Matías Fidemraizer Jun 22 '14 at 08:01
  • Oh, I was just curious to see if I was accidentally making a flood of objects without realizing, but I'm glad C# is smart enough to realize my intent. – Kyle Baran Jun 22 '14 at 08:01
  • 1
    @KyleBaran C# is god :D – Matías Fidemraizer Jun 22 '14 at 08:02
1

Can someone confirm, or is something more sinister happening?

It seems like it's another secret action within the events of new and 2nd Cold War!

Yes, your constructor is doing what you're describing in your question.

I would explain that the anonymous delegate is converted into Action<string> using delegate type inference.

For example, the following code line:

return (text) =>
        {
            Console.WriteLine(text);
        };

...is infered to:

return new Action<string>((text) => 
{
     Console.WriteLine(text);
});
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • I was expecting your "translation" to be incorrect in terms of it not maintaining the (externally visible) caching behaviour - but it does! This is an interesting case where `new` doesn't actually mean `new`... – Jon Skeet Jun 22 '14 at 08:03
  • @JonSkeet What do you mean with *externally visible* caching behavior? – Matías Fidemraizer Jun 22 '14 at 08:05
  • I mean that you can `object.ReferenceEquals(new SomeClass().SomeAction, new SomeClass().SomeAction)` returns `true`, which wouldn't be the case if a new delegate were generated for each class instance. – Jon Skeet Jun 22 '14 at 08:06
  • @JonSkeet I see. But, if I check your own sample in your answer, you show how CSC generates the anonymous delegate method behind the scene. At the end of the day, with or without delegate type inference, it should compile the same IL. Either with new or not new, the difference should be how the delegate type inference syntax sugar works. I mean, the result is the expected one, isn't it? – Matías Fidemraizer Jun 22 '14 at 08:25
  • It's not what I'd expect, no. I'd expect that every time I use `new`, I'll get a genuinely new instance. Otherwise the word loses its meaning, surely. This isn't just type inference - it's not like you're just *casting* the lambda expression to `Action`. You're using a delegate instance creation expression, which I'd expect to create a new instance. It's no big deal, but it surprised me. – Jon Skeet Jun 22 '14 at 08:42
  • @JonSkeet But, we should expect compiler to create a single implementation of the method backing the whole delegate. But now I understand your concern and now is mine too. Even if both instances uses the same compiled method, delegates should be different instances and, thus references... – Matías Fidemraizer Jun 22 '14 at 09:29
  • Yes, there'll be one method, but *could* be two delegate instances. The specification allows optimizations in some cases but not others. For example, a method group conversion currently *requires* a new instance, but that would be a really easy one to cache, for static methods. I need to look carefully to check whether your example actually demonstrates a bug :) – Jon Skeet Jun 22 '14 at 11:30
  • @JonSkeet, yes, it's clear that this is an edge case and it's hard to understand why two delegates are considered the same reference. Maybe I'm wrong, but rather than an optimization, this could how CLR handles delegates: just like pointers to methods. And it seems that, since anonymous delegates outputs the whole static method, the static part of the whole class shares the same "reference" within an AppDomain. That's the "problem" of byte code: it might execute differently than how it's declared... – Matías Fidemraizer Jun 22 '14 at 12:19
  • @JonSkeet BTW, if you find something interesting, I'll look forward for your conclusions! – Matías Fidemraizer Jun 22 '14 at 12:19
  • Found it. In section 7.6.10.5: "If E is an anonymous function, the delegate creation expression is processed in the same way as an anonymous function conversion (§6.5) from E to D." So basically it's treated like a cast. This seems very odd to me. – Jon Skeet Jun 22 '14 at 12:54
  • @JonSkeet I thought it was a compile-time only feature. I mean, I thought that if you create an anoymous delegate with or without `new [delegate type]`, CSC was compiling the same IL (as if `new` was used either way). But it's treated like a cast. Well, in fact, it's like a *cast* or conversion, because it feels like an implicit operator from anonymous delegate to *concrete* delegate using type inference. – Matías Fidemraizer Jun 22 '14 at 13:05