79

In C# language and .NET framework, could you help me with understanding delegates? I was trying to check some code, and found that the results I received were unexpected for me. Here it is:

class Program
{
    public static int I = 0;

    static Func<string> del = new Func<string>(I.ToString);

    static void Main(string[] args)
    {
        I = 10;
        Console.WriteLine("{0}", del());
    }
}

The answer was 0, but not 10. Why?

Cœur
  • 37,241
  • 25
  • 195
  • 267
user1859587
  • 771
  • 5
  • 9
  • I think you forgot `()` after `ToString` – Rotem Nov 28 '12 at 11:18
  • 12
    @Rotem: No, he didn't. – Daniel Hilgarth Nov 28 '12 at 11:19
  • 3
    @Rotem - It is a delegate declaration. Adding `()` would invoke `ToString`. – Oded Nov 28 '12 at 11:19
  • 1
    Sorry, never used `Func`s, was a guess :) – Rotem Nov 28 '12 at 11:20
  • Btw, I've also tried to use non-static field and delegate, but the result was the same. – user1859587 Nov 28 '12 at 11:21
  • 2
    +1 for a nice question, well asked. Great example of how a seemingly-simple question can highlight a poorly-understood area of the language/platform. – Martin Nov 28 '12 at 11:50
  • 5
    A (unicast) delegate instance can point either to an instance method or a `static` method. When it represents an instance method, the delegate holds **both** the "target" object on which to invoke the method, and the method info. So when you say `del = I.ToString;`, the `del` will hold the object `I` which is here an `Int32` (immutable value type). When you use an anonymous function, `del = () => I.ToString();`, the compiler creates a method `static string xxx() { return I.ToString(); }` and the `del` object holds that generated method. – Jeppe Stig Nielsen Nov 28 '12 at 12:18
  • 1
    (remark to previous comment) Actually, upon inspecting `del.Method` and `del.Target`, it looks like the compiler generates from `() => I.ToString()` both a nested type inside your `Program` class and a non-static method of that nested type. Both have weird names. But that's just a detail. – Jeppe Stig Nielsen Nov 28 '12 at 12:24
  • @JeppeStigNielsen: Thank you very much! I think it's clear for me now. – user1859587 Nov 28 '12 at 12:28
  • 1
    @JeppeStigNielsen: The fact that it is creating a nested type is an implementation detail that might already have changed at least once. In my sample using .NET 4 it just created a static method in the type that contains the delegate. – Daniel Hilgarth Nov 28 '12 at 12:34
  • @DanielHilgarth You're right. It actually does it the way I thought in my first comment here. I did a mistake when I tested it, because I captured a _local_ variable `i` in a delegate object I made _inside_ a method. Surely that's something different. The local variable is not so local after that ... So I accidentally made things more complicated than in the original setup. So _maybe_ this detail didn't change, I just made a mistake. – Jeppe Stig Nielsen Nov 28 '12 at 13:15
  • @JeppeStigNielsen: Yes, that makes sense. Capturing a local variable would require an anonymous class to be created, otherwise there would be no place to store it. – Daniel Hilgarth Nov 28 '12 at 13:17

5 Answers5

79

The reason is the following:

The way you declare the delegate it points directly to the ToString method of the static int instance. It is captured at the time of creation.

As flindeberg points out in the comments below, each delegate has a target and a method to be executed on the target.

In this case, the method to be executed is obviously the ToString method. The interesting part is the instance the method is executed on: It is the instance of I at the time of the creation, meaning that the delegate is not using I to get the instance to use but it stores the reference to the instance itself.

Later you change I to a different value, basically assigning it a new instance. This doesn't magically change the instance captured in your delegate, why should it?

To get the result you expect, you would need to change the delegate to this:

static Func<string> del = new Func<string>(() => I.ToString());

Like this, the delegate points to an anonymous method that executes ToString on the current I at the time of the execution of the delegate.

In this case, the method to be executed is an anonymous method created in the class in which the delegate is declared in. The instance is null as it is a static method.

Have a look at the code the compiler generates for the second version of the delegate:

private static Func<string> del = new Func<string>(UserQuery.<.cctor>b__0);
private static string cctor>b__0()
{
    return UserQuery.I.ToString();
}

As you can see, it is a normal method that does something. In our case it returns the result of calling ToString on the current instance of I.

JYelton
  • 35,664
  • 27
  • 132
  • 191
Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • Am I write in thinking, that `static Func del = new Func(I.ToString);` will make boxing of `I` and del will always link to ToSting method of those time boxed `I`? – user1859587 Nov 28 '12 at 11:33
  • Interesting, and this could lead to some weird garbage collector behavior if you are not getting rid of your delegates properly :) – flindeberg Nov 28 '12 at 11:35
  • I was thinking of boxing, because I've also tried an example with `static Func del = new Func(Program.GetI)`, where `Program.GetI` is static func, that returns static `I`. And result was 10. That's why now, to my mind, linking delegate with I.ToString leads to boxing I and using of anonymous method leads also to boxing, but only in the place where I invoke the delegate with this anonymous method. Is it right? Or should I read my books again? :)) – user1859587 Nov 28 '12 at 11:49
  • @user1859587: Boxing is the process of creating a reference type instance out of a value type instance. – Daniel Hilgarth Nov 28 '12 at 11:51
  • @user1859587: I ran some tests and the results are the same whether we have unboxed int, boxed int and Nullable and regardless of whether they are static or not (using Roslyn compiler though). – flindeberg Nov 28 '12 at 11:52
  • 1
    @flindeberg: You can even use your own class instead of int. It will still behave the same, because the underlying reason doesn't change: The delegate points to the specific incarnation of ToString on one specific object. It doesn't matter whether this is a reference type or a value type. – Daniel Hilgarth Nov 28 '12 at 11:54
  • I'm sorry, But why using of anonymous method won't lead to the same? Is linking delegate with function also links it with the specific incarnation of method on one specific object? Is it means, that when I link delegate with some object's method and change this object, the delegate will link on old method? – user1859587 Nov 28 '12 at 11:59
  • @DanielHilgarth: Exactly, I myself became unsure of how the boxing/unboxing would be handled though, so I had to check for myself :) – flindeberg Nov 28 '12 at 12:10
  • 3
    @user1859587: A delegate has a method and a target (instance), it matters if it captures your instance or the instance of the lambda function with in turn contains references to the instance. – flindeberg Nov 28 '12 at 12:13
  • @DanielHilgarth: When becoming a target of a delegate it seems that a boxed version of the `int` gets stored as target though (which explains the behaviour, since if it is referencing a stack-stored unboxed int the value would change). – flindeberg Nov 28 '12 at 12:17
  • @flindeberg: Seems like it. The [IL](http://pastebin.com/7pCaGgif) supports that. – Daniel Hilgarth Nov 28 '12 at 12:21
  • @DanielHilgarth: Thank you for your patience and for your help! – user1859587 Nov 28 '12 at 12:29
  • 1
    @user1859587: You are welcome. BTW: I tried to update the answer to make it a bit clearer what is going on here. You might want to re-read it :-) – Daniel Hilgarth Nov 28 '12 at 12:31
  • 3
    Daniel, just to confirm flindeberg's comment: your answer is correct but your comments regarding boxing are not. user1859587 is correct: the observed behaviour is a consequence of the fact that the delegate captures the receiver of the call. Though the receiver of a call to ToString on int would be a reference to an int variable, the delegate has no way of putting a reference to an int variable on the heap; references to *variables* may only go into temporary storage. So it does the next best thing: it boxes the int and makes a reference to that heap location. – Eric Lippert Nov 28 '12 at 15:19
  • 1
    @EricLippert: Thanks for the clarification. What I meant to say with my comments about "boxing" vs. "capture" is that the behavior the OP is seeing has nothing to do with the fact that `I` is a value type. And this statement should be correct. – Daniel Hilgarth Nov 28 '12 at 15:30
  • 1
    That's correct. The delegate captures the value, not the variable. – Eric Lippert Nov 28 '12 at 15:42
  • 10
    An interesting consequence of the fact that the receiver is boxed is that it is impossible to make a delegate to GetValueOrDefault() on a nullable int, because boxing a nullable int produces a boxed int, not a boxed nullable int, and a boxed int has no GetValueOrDefault() method. – Eric Lippert Nov 28 '12 at 16:01
  • 1
    @EricLippert: Very interesting. There is even a specific compiler message for this: "Cannot bind delegate to 'System.Nullable.GetValueOrDefault()' because it is a member of 'System.Nullable'" – Daniel Hilgarth Nov 28 '12 at 17:15
  • 1
    And this is why we'll never see an obfuscated C# competition - delegates would make it too easy. – Sam Skuce Nov 28 '12 at 17:15
4

You need to pass in I to your function so that I.ToString() can be executed at the appropriate time (instead of at the time function is created).

class Program
{
    public static int I = 0;

    static Func<int, string> del = num => num.ToString();

    static void Main(string[] args)
    {
        I = 10;
        Console.WriteLine("{0}", del(I));
    }
}
Dave New
  • 38,496
  • 59
  • 215
  • 394
1

Here is how this should be done:

using System;

namespace ConsoleApplication1
{

    class Program
    {
        public static int I = 0;

        static Func<string> del = new Func<string>(() => {
            return I.ToString();
        });

        static void Main(string[] args)
        {
            I = 10;
            Console.WriteLine("{0}", del());
        }
    }
}
Alex Filipovici
  • 31,789
  • 6
  • 54
  • 78
0

C# delegate enable encapsulate both an object and instance and a method. A delegate declaration defines a class that is derived from the class System.Delegate. A delegate instance encapsulates an invocations list, which is a list one or more method, each of which is referred to as callable entity.

learn more form

http://asp-net-by-parijat.blogspot.in/2015/08/what-is-delegates-in-c-how-to-declare.html

-2

My guess is because int are passed by values not references, and for that reason when creating the delegate, it's a delegate to the method ToString of the current value of "I" (0).

Yshayy
  • 325
  • 2
  • 10
  • 2
    Your guess is not correct. This has nothing to do with value types vs. reference types. The exact same thing would also happen with reference types. – Daniel Hilgarth Nov 28 '12 at 11:49
  • Actually it is, if for example we use a class instance and ToString was manipulating the instance data for return value, it would produce the return value of the current class state, not the state of the class when the delegate was created. The function doesn't ran when the delegate is created and there's only one instance of the class. – Yshayy Nov 28 '12 at 12:15
  • Func(() => I.ToString()) Should work as well because we are not using "I" until the method invocation. – Yshayy Nov 28 '12 at 12:17
  • But that is not an equivalent to what is going on here. If you would use the class `Foo` instead of `int` and changed the line `I = 10` to `I = new Foo(10)` you would have the exact same result as with the current code. `I.Value = 10` is something else completely. This doesn't assign a new instance to `I`. But assigning a new instance to `I` is the important point here. – Daniel Hilgarth Nov 28 '12 at 12:18
  • Yes, that delegate works as I already showed in my answer quite some time before you posted yours. – Daniel Hilgarth Nov 28 '12 at 12:19
  • 2
    ok, So the problem is reassigning "I", if "I" were mutable and we change the object without reassigning I, it would've work. in this example, we can't do it because I is int (immutable). – Yshayy Nov 28 '12 at 12:25