C# 2.0 - Anonymous Methods

[ 2005-10-20 15:55:25 | 作者: yuhen ]
字号: | |
本文一些资料来源于李建忠先生的讲演稿。

1. 什么是匿名方法?

匿名方法允许我们以一种“内联”的方式来编写方法代码,将代码直接与委托实例相关联,从而使得委托实例化的工作更加直观和方便。

我们先看一段代码。
private void button1_Click(object sender, EventArgs e)
{
    MessageBox.Show("OK!");
}

private void Form1_Load(object sender, EventArgs e)
{
    this.button1.Click += new EventHandler(this.button1_Click);
}

上面的代码是一个典型的事件(委托)方法,虽然Click事件代码非常简单,但是我们还是必须写一个这样的方法,并创建一个委托对象。在2.0中,提供了一种称之为匿名方法的措施来改进类似的代码。
private void Form1_Load(object sender, EventArgs e)
{
    this.button1.Click += delegate
    {
        MessageBox.Show("OK!");
    }
}

是不是更简单?我们反编译一下会发现,编译器依然创建了一个委托字段和方法,说白了就是编译器替我们完成了原本需要我们输入的代码。呵呵,偷懒无罪,偷懒有理!
[CompilerGenerated]
private static EventHandler <>9__CachedAnonymousMethodDelegate1;
 
private void Form1_Load(object sender, EventArgs e)
{
    if (Form1.<>9__CachedAnonymousMethodDelegate1 == null)
    {
        Form1.<>9__CachedAnonymousMethodDelegate1 = new EventHandler(Form1.<Form1_Load>b__0);
    }
    this.button1.Click += Form1.<>9__CachedAnonymousMethodDelegate1;
}
 
[CompilerGenerated]
private static void <Form1_Load>b__0(object, EventArgs)
{
    MessageBox.Show("OK!");
}
 

那么匿名方法可以在那些地方使用呢?继续往下看。
public void Start()
{
    Thread t = new Thread(new ThreadStart(ThreadProc));
    t.Start();
}

private void ThreadProc()
{
    Console.WriteLine("Hello, World!");
}

我们可以使用匿名方法改写为:
public void Start()
{
    Thread t = new Thread(delegate() // 由于Thread有多个构造方法,因此指定参数列表。
        {
            Console.WriteLine("Hello, World!");
        });
    t.Start();
}

2. 参数列表

匿名方法可以在delegate关键字后面跟上委托签名的参数列表(可以不指定),后面的代码块则可以访问这些参数。
private void Form1_Load(object sender, EventArgs e)
{
    this.button1.Click += delegate(object sender, EventArgs e)
    {
        MessageBox.Show((Button)sender.Text + " Clicked!");
    }
}

注意:
(1) 参数要么省略,要么必须全部添加,和委托签名必须保持一致。
(2) 不指定方法列表和参数列表为空是不同的。
delegate {...}  // 不指定参数列表
delegate() { ... } // 参数列表为空

3. 返回值

如果委托类型的返回值为void,匿名方法里便不能返回任何类型;
如果委托类型的返回值不是void,匿名方法里的返回值必须和委托类型相一致;
public delegate int TestHandler(int x);

static void Test()
{
    TestHandler t = delegate(int x)
    {
        return x * 2;
    };

    int i = t(2);
    Console.WriteLine(i);
}

4. 外部变量

一些局部变量和参数有可能被匿名方法所使用,他们被称为“匿名方法的外部变量”。
外部变量的生存期会由于“匿名方法的捕获效益”而延长——直到委托实例不再被引用为止。
public class Program
{
    public delegate int TestHandler(int x);

    static void Test(int i)
    {
        TestHandler t = delegate
        {
            Console.WriteLine(++i);
            return 0;
        };

        Console.WriteLine(i);
    }

    static void Main(string[] args)
    {
        Test(0);
    }
}

上面的代码中,Test方法的参数"i"被匿名方法所使用,因此i就是匿名方法的外部变量。
猜测一下最后输出的结果是多少?"1, 1"?还是"1, 0"?那么你错了,正确的结果应该是"0",为什么?因为我们仅仅创建了一个匿名方法,并没有去调用这个委托。:-) 好了,在Console.WriteLine(i) 前面加一个 "t(i);" 这回就对了。
public class Program
{
    public delegate int TestHandler(int x);

    static void Test(int i)
    {
        TestHandler t = delegate
        {
            Console.WriteLine(++i);
            return 0;
        };

        t(i); // <---------- Add Here!!!
        Console.WriteLine(i);
    }

    static void Main(string[] args)
    {
        Test(0);
    }
}

注意,"i"的值在被匿名方法调用后发生了变更,可我们并没有在"t(i);"语句中使用 ref 或者 out 关键字,这和你熟悉的拷贝传递有所不同是吗?看看反编译的代码你就明白了。
public class Program
{
    public int i;

    [CompilerGenerated]
    private sealed class <>c__DisplayClass1
    {
        public int <Test>b__0(int)
        {
            int num2;
            this.i = num2 = this.i + 1;
            Console.WriteLine(num2);
            return 0;
        }
    }

    public delegate int TestHandler(int x);

    private static void Test(int i)
    {
        <>c__DisplayClass1 class1 = new <>c__DisplayClass1();
        class1.i = i;
        TestHandler handler1 = new TestHandler(class1.<Test>b__0);
        handler1(class1.i);
        Console.WriteLine(class1.i);
    }

    private static void Main(string[] args)
    {
        Program.Test(0);
    }
}

看了反编译的结果,你会发现使用外部变量的成本很高,而且会带来潜在的危险,因此不推荐大规模使用。

5. 委托类型的推断

C# 2.0 允许我们在进行委托实例化时,省略掉委托类型,而直接采用方法名,C#编译器会做合理的推断。
public delegate void TestHandler(int x);

public class Program
{
    static void Test(int i)
    {
    }

    static void Execute(TestHandler test, int i)
    { 
    }

    static void Main(string[] args)
    {
        // C# 1.x 代码
        TestHandler t1 = new TestHandler(Test);
        Execute(new TestHandler(Test), 10);

        // C# 2.0 代码
        TestHandler t2 = Test;  // 注意Test不包含括号,否则就成方法调用了。
        Execute(Test, 20);
    }
}

看看反编译后的Main方法中 2.0 的代码样子。
private static void Main(string[] args)
{
    TestHandler handler1 = new TestHandler(Program.Test);
    Program.Execute(new TestHandler(Program.Test), 20);
}

呵呵,还是偷懒无敌啊。
[最后修改由 yuhen, 于 2010-05-31 12:47:06]
评论Feed 评论Feed: http://www.rainsts.net/feed.asp?q=comment&id=111

这篇日志没有评论。

发表评论
表情图标
[smile] [confused] [cool] [cry]
[eek] [angry] [wink] [sweat]
[lol] [stun] [razz] [redface]
[rolleyes] [sad] [yes] [no]
[heart] [star] [music] [idea]
UBB代码
转换链接
表情图标
悄悄话
用户名:   密码:  
验证码 * 请输入验证码