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?

12
  • 12
    @Rotem: No, he didn't. Commented Nov 28, 2012 at 11:19
  • 3
    @Rotem - It is a delegate declaration. Adding () would invoke ToString. Commented Nov 28, 2012 at 11:19
  • 1
    Sorry, never used Funcs, was a guess :) Commented Nov 28, 2012 at 11:20
  • 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. Commented Nov 28, 2012 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. Commented Nov 28, 2012 at 12:18

4 Answers 4

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.

Sign up to request clarification or add additional context in comments.

19 Comments

@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.
@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.
@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, 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.
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.
|
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)); } } 

Comments

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

Comments

-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).

6 Comments

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.
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.
Func<string>(() => I.ToString()) Should work as well because we are not using "I" until the method invocation.
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.
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).
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.