Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
438 views
in Technique[技术] by (71.8m points)

c# - Integer handled as reference type when passed into a delegate

I was attending the TechDays 2013 in the Netherlands this week and I got an interesting quiz question presented. The question was: What is the output of the following program. Here is what the code looks like.

class Program
{
    delegate void Writer();

    static void Main(string[] args)
    {
        var writers = new List<Writer>();
        for (int i = 0; i < 10; i++)
        {
            writers.Add(delegate { Console.WriteLine(i); });
        }

        foreach (Writer writer in writers)
        {
            writer();
        }
    }
}

Obviously, the answer I gave was wrong. I argumentend, because int is a value type, the actual value that is passed into Console.WriteLine() gets copied, so the output would be 0...9. However i is handled as a reference type in this situation. The correct answer is that it will display ten times 10. Can anyone explain why and how?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I argumentend, because int is a value type, the actual value that is passed into Console.WriteLine() gets copied

That is exactly correct. When you call WriteLine the value will be copied.

So, when are you calling WriteLine? It's not in the for loop. You're not writing anything at that point in time, you're just creating a delegate.

It's not until the foreach loop when you invoke the delegate, it's at that time that the value in the variable i is copied to the stack for the call to WriteLine.

So, what's the value of i during the foreach loop? It's 10, for each iteration of the foreach loop.

So now you're asking, "well how is i anything during the foreach loop, isn't it out of scope. Well, no, it's not. What this is demonstrating is a "closure". When an anonymous method reference a variable that variable's scope needs to last for as long as that anonymous method, which could be for any period of time. If nothing special is done at all reading the variable would be random garbage containing whatever happened to be stuck in that location in memory. C# actively makes sure that situation can't happen.

So what does it do? It creates a closure class; it's a class that will contain a number of fields representing everything that is closed over. In other words, the code will be refactored to look something like this:

public class ClosureClass
{
    public int i;

    public void DoStuff()
    {
        Console.WriteLine(i);
    }
}

class Program
{
    delegate void Writer();

    static void Main(string[] args)
    {
        var writers = new List<Writer>();
        ClosureClass closure = new ClosureClass();
        for (closure.i = 0; closure.i < 10; closure.i++)
        {
            writers.Add(closure.DoStuff);
        }

        foreach (Writer writer in writers)
        {
            writer();
        }
    }
}

Now we both have a name for our anonymous method (all anonymous methods are given a name by the compiler) and we can ensure that the variable will live for as long as the delegate that refers to the anonymous function lives.

Looking at this refactor, I hope it's clear why the result is that 10 is printed 10 times.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...