Hello World
Spiga

从.NET中委托写法的演变谈开去(上):委托与匿名方法

2009-08-05 12:50 by 老赵, 18539 visits

在《关于最近面试的一点感想》一文中,Michael同学谈到他在面试时询问对方“delegate在.net framework1.1,2.0,3.5各可以怎么写”这个问题。于是乎,有朋友回复道“请问楼主,茴香豆的茴有几种写法”,“当代孔乙己”,独乐,众乐。看了所有的评论,除了某些朋友认为“的确不该不知道这个问题”之外,似乎没有什么人在明确支持楼主。

不过我支持,为什么?因为我也提过出这样的问题。

这样,我们暂且不提应聘“高级开发人员”的人,在“自称熟悉各版本.NET框架”的前提下,是否应该知道这个答案。我们也暂且不提Michael同学提问的“目的”是什么。老赵就先单独针对这个问题进行解释,然后谈谈自己为什么会提出这个问题吧。

可能有一件事情需要说在前面,那就是:委托本身其实从来没有改变过,改变的一直都是委托的“写法”。因此更确切地说,改变的只是“编译器”。而本文所有内容都用C#来实现,其实谈得也都是C#编译器本身——但是其实VB.NET也有变化啊。再由于.NET版本和C#版本的关系也是非常密切的,因此全文就使用.NET版本进行指代了。

.NET 1.x中委托的写法

委托,如果不追究细节,从表面上来看我们可以将其通俗地理解为一个安全的“函数指针”。当然,这个函数指针其实也是一个对象,有自己的成员,也会封装了被调用方的上下文等等。至于委托的定义和使用方式,则是这样的:

public delegate int SomeDelegate(string arg1, bool arg2);

public static int SomeMethod(string arg1, bool arg2) { return 0; }

public class SomeClass
{
    public int SomeMethod(string a1, bool a2) { return 0; }

    public event SomeDelegate SomeEvent;
}

static void Main(string[] args)
{
    SomeClass someClass = new SomeClass();
    SomeDelegate someDelegate = new SomeDelegate(someClass.SomeMethod);

    someClass.SomeEvent += new SomeDelegate(SomeMethod);
}

可见,在.NET 1.x中需要使用new DelegateType(...)的方式来创建一个委托对象。不过,作为委托对象内部的方法它既可以是实例方法,也可以是静态方法。此外,方法只需要匹配委托类型的签名和返回值即可,方法参数的名称不会成为约束。

嗯,就是这么简单。

匿名方法及回调简化

.NET 2.0中的委托引入了范型,且写法略有简化:

public delegate TResult MyFunc<T1, T2, TResult>(T1 a1, T2 a2);

public static int SomeMethod(string a1, bool a2) { return 0; }

static void Main(string[] args)
{
    MyFunc<string, bool, int> myFunc = SomeMethod;
}

在.NET 2.0中,new DelegateType已经可以省略,开发人员可以直接将方法赋值给一个委托对象的引用。当然,这个改进不值一提,C# 2.0中委托写法的关键在于引入了“匿名方法”:

public static void TestRequest(string url)
{
    WebRequest request = HttpWebRequest.Create(url);
    request.BeginGetResponse(delegate(IAsyncResult ar)
    {
        using (WebResponse response = request.EndGetResponse(ar))
        {
            Console.WriteLine("{0}: {1}", url, response.ContentLength);
        }
    },
    null);
}

匿名方法,简单地说就是内联在方法内部的委托对象,它的关键便在于形成了一个闭包(委托执行时所需的上下文)。如上面的代码中,BeginGetResponse的第一个参数(委托)可以直接使用TestRequest方法的参数url,以及方法内的“局部”变量request。如果没有匿名函数这个特性的话,代码写起来就麻烦了,例如在.NET 1.x中您可能就必须这么写:

展开

此时,我们往往会发现,开发人员需要花费大量的精力,为一小部分代码维护一大段上下文。例如在这段代码中,我们会将url和request对象塞入一个object数组中,在回调函数中再通过危险的Cast操作恢复数据。如果您希望“强类型”,那么只能为每个回调创建一个新的上下文对象,维护起来可能更加麻烦——要知道,在并行编程,异步调用越来越重要的今天,如果没有匿名方法自动保留上下文的特性,开发人员会为这些“额外工作”疲于奔命的。

可能您会说,匿名方法的可读性不佳,因为需要“内联”。一个方法中内联太多,维护成本就上去了,所以匿名方法并不推荐使用。我想说的是,您错了。如果为了可维护性,要将方法独立拆开,也可以利用匿名方法的优势:

public static void TestRequest(string url)
{
    WebRequest request = HttpWebRequest.Create(url);
    request.BeginGetResponse(delegate(IAsyncResult ar)
    {
        TestAsyncCallback(ar, request, url);
    }, null);
}

public static void TestAsyncCallback(IAsyncResult ar, WebRequest request, string url)
{
    using (WebResponse response = request.EndGetResponse(ar))
    {
        Console.WriteLine("{0}: {1}", url, response.ContentLength);
    }
}

如果借助.NET 3.5中的Lambda表达式,代码可以写的更简单易读:

public static void TestRequest(string url)
{
    WebRequest request = HttpWebRequest.Create(url);
    request.BeginGetResponse(ar => TestAsyncCallback(ar, request, url), null);
}

匿名方法使用案例:延迟初始化器

千万不要小看匿名方法的作用,有些时候您认为它的作用仅限于上文描述,只是因为没有在某些问题上踏前一步。例如,对于那些只需要“按需创建”,且要“线程安全”的对象,您会怎么做呢?没错,可以使用Double Check:

private object m_mutex = new object();
private bool m_initialized = false;
private BigInstance m_instance = null;

public BigInstance Instance
{
    get
    {
        if (!this.m_initialized)
        {
            lock (this.m_mutex)
            {
                if (!this.m_initialized)
                {
                    this.m_instance = new BigInstance();
                    this.m_initialized = true;
                }
            }
        }

        return this.m_instance;
    }
}

嗯,做的很漂亮!那么……这样的属性再来一个,再来三个,再来五个呢?可能有些朋友就会开始大段地Copy & Paste,于是错误便难免了。这里有一件真人真事,以前某位同学在一堆这样的代码中迷茫了,说为什么用了这种方法,还是初始化了多次对象了?检查了半天没有看出问题来。最后发现,原因是访问了错误的initialized变量(例如,在某个应该访问artistInitialized的地方访问了articleInitialized)。可惜,大段时间已经被浪费了——更糟的是,心情也随之变差了。

其实,Copy & Paste很明显没有遵守DRY原则啊。为什么不把它们封装在一处呢?例如:

展开

于是,之前的代码就可以简化成这样了:

private Lazy<BigInstance> m_lazyInstance =
    new Lazy<BigInstance>(delegate { return new BigInstance(); });

public BigInstance Instance { get { return this.m_lazyInstance.Value; } }

还是太丑,上Lambda表达式!

private Lazy<BigInstance> m_lazyInstance =
    new Lazy<BigInstance>(() => new BigInstance());
public BigInstance Instance { get { return this.m_lazyInstance.Value; } }

如果没有匿名方法,许多容易使用的编程模型和方式都难以开展。例如,我们就不会有CacheHelper,也不会有AsyncTaskDispatcher(上),也很难利用“延迟”所带来的便利,更难以出现微软并行扩展、CCR等优秀框架。可以这么说,如果您不善于使用委托,您如果不知道如何合适地使用匿名方法,您在不自知的情况下可能就已经编写了大量额外的代码了。

老赵平时的工作之一,便是为项目提供各种扩展API,可以让程序员们更愉快地进行开发工作,得到更好的生产力,让代码变得更加美好。如今C#有了匿名方法、Lambda表达式、表达式树、扩展方法等优秀的语言特性,真让我有“如鱼得水”的感觉。因此,我对于Java这样不思进取的语言可以说深恶痛绝(Java朋友们赶快学习Scala吧)。在看阅读大量Java开源项目代码时,我常有这样的感觉:“如果是C#的话,利用匿名方法,这个类不就可以不写,那个类就可以省略……”。没错,为了保留回调函数的上下文而创建一些类,对于C#程序员来说,的确是一件有些不可思议的事情。

至于Lambda表达式以及其他话题,我们下次再说吧。

匿名方法的缺点

匿名方法的优势在于自动形成闭包,而它的缺点也是让程序员“不自觉”地创建了闭包,这会让某些对象的生命周期加长。例如在一开始的TestRequest方法中,表面上看起来url是参数,request是局部变量,有些朋友可能会认为它们在方法退出后就已经准备回收了。不过因为形成了闭包,url和request已经“升级”为一个对象的域变量,它的生命周期延长了,延长至回调函数执行完毕。因此,一不注意可能就会产生一些莫名其妙的情况

其实,这些都是“延迟”所带来的陷阱,作为一个优秀的开发人员,除了知道某个东西的作用和优势,也要知道它的问题,不是吗?

总结

您现在还觉得.NET中的“委托”是一个简单的,只适合“初学者”,一搜就都知道的概念吗?

您可能会说“是”,不过对我来说这真不简单,我也是慢慢才意识到这些的。虽然关于委托的大量内容可以在互联网上搜索而来,但还是有太多东西是需要在大量编程实践中积累下来的——等一下,这不就是“高级开发人员”和“初学者”的主要区别之一吗?

嘲笑孔乙己的朋友们,你们在一味鄙视“茴”的四种写法的同时,说不定也失去了一个了解中国传统文化的机会呢!

剩下的下次再说吧,Lambda表达式还等着我们。

相关文章

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

129 条回复

  1. 在别处
    *.*.*.*
    链接

    在别处 2009-08-05 12:54:00

    居然这么快就出来了

  2. crabo[未注册用户]
    *.*.*.*
    链接

    crabo[未注册用户] 2009-08-05 12:57:00

    相当赞的一篇

    委托很多用, 不过Lambda式对我则太过了

  3. 打酱油的[未注册用户]
    *.*.*.*
    链接

    打酱油的[未注册用户] 2009-08-05 12:59:00

    那篇面试问题的回帖中没人说“.NET中的‘委托’是一个简单地可以忽视的东西”,只有人说“从来没用过”。这完全是两回事吧?

  4. Grove.Chu
    *.*.*.*
    链接

    Grove.Chu 2009-08-05 13:06:00

    真没有想到动作这么快,学习中。

  5. 阿龍
    *.*.*.*
    链接

    阿龍 2009-08-05 13:10:00

    纯属玩技术。

  6. Goodspeed
    *.*.*.*
    链接

    Goodspeed 2009-08-05 13:12:00

    总结的不错。

    虽然从1.1到3.5都用过,但面试估计很难写起来。特别是1.1的,基本已经忘记怎么写啦。

  7. Gnie
    *.*.*.*
    链接

    Gnie 2009-08-05 13:14:00

    那些面试的赶快背下来,面试去吧:)

  8. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-05 13:15:00

    老赵你速度太快了,我正写着呢,看来没必要继续了

  9. 老赵
    admin
    链接

    老赵 2009-08-05 13:20:00

    打酱油的:那篇面试问题的回帖中没人说“.NET中的‘委托’是一个简单地可以忽视的东西”,只有人说“从来没用过”。这完全是两回事吧?


    我是针对那些“孔乙己”的说法,还有认为委托是一搜一学马上就会的,认为这是初学者才知道,观察不出开发人员能力的说法来谈的。
    那些观点我理解下来就是“委托是一个简单的可以被忽视的东西”,面试时不用问。

  10. 老赵
    admin
    链接

    老赵 2009-08-05 13:21:00

    在别处:居然这么快就出来了


    全是脑子的旧货,整理一下就写出来了,大约1个半小时左右。

  11. 老赵
    admin
    链接

    老赵 2009-08-05 13:21:00

    crabo:
    委托很多用, 不过Lambda式对我则太过了


    我认为不是,Lambda表达式是很重要的特性,下一篇我会说说的。

  12. 老赵
    admin
    链接

    老赵 2009-08-05 13:22:00

    阿龍:纯属玩技术。


    没错没错,技术很好玩,玩出美感来更爽快。

  13. 老赵
    admin
    链接

    老赵 2009-08-05 13:22:00

    麒麟.NET:老赵你速度太快了,我正写着呢,看来没必要继续了


    这就交给我吧,呵呵。

  14. 老赵
    admin
    链接

    老赵 2009-08-05 13:23:00

    Goodspeed:
    虽然从1.1到3.5都用过,但面试估计很难写起来。特别是1.1的,基本已经忘记怎么写啦。


    嗯,其实最重要的东西是从匿名方法开始的。
    不过1.1的做法应该也不会忘吧。

  15. 111222aaaaaaaaaaaaaaa[未注册用户…
    *.*.*.*
    链接

    111222aaaaaaaaaaaaaaa[未注册用户] 2009-08-05 13:24:00

    呼唤人肉搜索奇葩小赵

  16. 老赵
    admin
    链接

    老赵 2009-08-05 13:24:00

    Gnie:那些面试的赶快背下来,面试去吧:)


    这东西我认为是很难背下来的,还是要有自己体会的,因为提问方式各种各样阿,呵呵。

  17. 阿龍
    *.*.*.*
    链接

    阿龍 2009-08-05 13:25:00

    Jeffrey Zhao:

    阿龍:纯属玩技术。


    没错,技术很好玩。


    玩技术很累。版本总是在更新。不知道等你60岁了还玩得动不。。。

  18. billlo[未注册用户]
    *.*.*.*
    链接

    billlo[未注册用户] 2009-08-05 13:27:00

    先頂老趙再看

  19. 青羽
    *.*.*.*
    链接

    青羽 2009-08-05 13:30:00

    一会儿过来看(下)。

  20. 老赵
    admin
    链接

    老赵 2009-08-05 13:35:00

    阿龍:
    玩技术很累。版本总是在更新。不知道等你60岁了还玩得动不。。。


    那时候退休养老去了。
    技术不新,没有东西是突然冒出来难以理解的,新技术的设计人员也是根据旧有东西改进,演变的。
    所以我真的没有在.NET 1 => .NET 3.5的过程中遇到过困难,自从.NET 2.0发布开始,我唯一看过的直接与C#/.NET相关的书也只有CLR via C#了。
    C# 2.0,asp.net 2.0等东西我一点书都没有看过,看得都是文档,博客等等。

  21. 新手中的新手
    *.*.*.*
    链接

    新手中的新手 2009-08-05 13:38:00

    复习+学习一遍,感觉不错!呵呵!

  22. 阿龍
    *.*.*.*
    链接

    阿龍 2009-08-05 13:39:00

    Jeffrey Zhao:
    那时候退休养老去了。
    技术不新,没有东西是突然冒出来难以理解的,新技术的设计人员也是根据旧有东西改进,演变的。
    所以我真的没有从.NET 1 => .NET 3.5的过程中遇到过困难,自从.NET 2.0发布开始,我唯一看过的书也只有CLR via C#了。
    C# 2.0,asp.net 2.0等东西我一点书都没有看过,看得都是文档,博客等等。



    老赵,牛人也。。。
    比你讲微软的课来的有意思哈。

  23. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-05 13:44:00

    其实委托一直就没改变过,只是编译器给我们提供了便捷的语法而已,这也是我看那篇面试文章的想法。匿名委托和蓝不大都出来很久了,但是我还是没怎么用过。毕竟也不是必须用不可。
    从code review的观点来说,有些随意。这样的代码对于不熟悉委托的人,是比较抽象的,还是需要一板一眼的来写代码,也就是老赵说的多维护一些代码。语法简单的背后增加了代码的思考复杂度,宁愿笨人用笨方法。

    不过还是支持老赵的文章。

  24. 老赵
    admin
    链接

    老赵 2009-08-05 13:46:00

    阿龍:
    比你讲微软的课来的有意思哈。


    我微软讲的东西也是这样的啊,会加入许多自己的看法,按部就班讲文档意义不大,还不如自己看文档效率高。

  25. 老赵
    admin
    链接

    老赵 2009-08-05 13:47:00

    @DiggingDeeply
    我是真离不开匿名委托和Lambda表达式了。

  26. 蜗牛身上的一只蚂蚁
    *.*.*.*
    链接

    蜗牛身上的一只蚂蚁 2009-08-05 13:51:00

    青羽:一会儿过来看(下)。


    为何我这里没有引用图片的按键呢。。

  27. wrafe
    *.*.*.*
    链接

    wrafe 2009-08-05 13:52:00

    老赵的技术很全面啊!

  28. wrafe
    *.*.*.*
    链接

    wrafe 2009-08-05 13:53:00

    不知道是从JAVA转C#的,还是双修啊.

  29. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-05 13:53:00

    @Jeffrey Zhao
    呵呵,我们都知道你上瘾了,沉醉于其中不能自拔。

  30. 老赵
    admin
    链接

    老赵 2009-08-05 13:56:00

    @wrafe
    04年前我干过一年多Java,不过一直对各种平台各种技术感兴趣。

  31. Steven Chen
    *.*.*.*
    链接

    Steven Chen 2009-08-05 14:08:00

    之所以我还能再这行混口饭吃,就是因为这行水货太多了,5年工作经验不知道delegate,就这现状,绝对有口饭吃......

  32. 丰色日月
    *.*.*.*
    链接

    丰色日月 2009-08-05 14:09:00

  33. Mainz
    *.*.*.*
    链接

    Mainz 2009-08-05 14:11:00

    厉害,茴香豆的三种写法都有了

  34. xiao_p
    *.*.*.*
    链接

    xiao_p 2009-08-05 14:12:00

    至少我认为一个做.net 5年的人,不知道delegate的写法那就是过分。

  35. wrafe
    *.*.*.*
    链接

    wrafe 2009-08-05 14:29:00

    @xiao_p
    每个人的侧重点不一样,现在基于.net平台开发已经有很多个方面,比如说基于MOSS开发的人,可能更了解一些WEB Part,工作流,Office集成开发等.他们至少会有很强的.Net技术,才能进行MOSS开发,工作年限也很长,如果考记忆不一定知道delegate的写法,但是你给他一个delegate事例他应该知道用的妥不妥当.

  36. 舟渡苦海
    *.*.*.*
    链接

    舟渡苦海 2009-08-05 14:33:00

    学习中..........

  37. 老赵
    admin
    链接

    老赵 2009-08-05 14:36:00

    @wrafe
    我认为delegate写法属于C#基础,不了解这个表示没有想过很多重要的问题,只是按部就班地写死程序。
    如果说忘了delegate怎么用,好比说忘了C#的foreach怎么用,实在是说不过去。

  38. craboYang
    *.*.*.*
    链接

    craboYang 2009-08-05 14:52:00

    Jeffrey Zhao:

    crabo:
    委托很多用, 不过Lambda式对我则太过了


    我认为不是,Lambda表达式是很重要的特性,下一篇我会说说的。



    那下次你说说Lambda的优势,哪些是匿名函数无法做到的。

    就像你说的, 我也搞了一年Java,公司也是Java/C#两大中心,人员互相调用,资源共享都是很正常的。 我排斥Lambda,觉得他带来的代码减少,但提供的语义也相应没体现, 需要Java人员也了解C#语法。

    所以,我架构这块都在追求良好封装的同时,也要保证代码的一般性可读。

  39. Kevin Dai
    *.*.*.*
    链接

    Kevin Dai 2009-08-05 14:52:00

    说实话“一个做.net 5年的人,不知道delegate的写法”这种情况,的确让人不可思议。怪不得他们做了几年,就叫累,想转行了,悲哀啊!

  40. 老赵
    admin
    链接

    老赵 2009-08-05 14:58:00

    @craboYang
    通过写Java-like C#代码来实现Java程序员理解C#代码是不是有点问题啊,应该让Java程序员学习C#咯。
    Unix大拿们不是一直这么声称:基于必要的了解才能谈“可读性”,“优雅”的吗?我认为有一定道理的。
    下次我会好好谈谈Lambda表达式的,但我还是认为,不应该为了让Java程序元看懂而丧失C#的生产力。

  41. Nick Wang (懒人王)
    *.*.*.*
    链接

    Nick Wang (懒人王) 2009-08-05 15:14:00

    我觉得lambda的可读性很好啊,除非是你写的不好

  42. Michael Tao
    *.*.*.*
    链接

    Michael Tao 2009-08-05 15:51:00

    呵呵,我始终觉得,这个世界上要是多几个老赵该多好啊

  43. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-08-05 15:53:00

    我是支持那位博主的,因为我也问过这样的问题

  44. HedgeHog
    *.*.*.*
    链接

    HedgeHog 2009-08-05 15:53:00

    这东西不好背啊.
    还是要在实战中体会到,才能写出来.

  45. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-08-05 15:57:00

    我的观点和老赵一致……

  46. adwyz
    *.*.*.*
    链接

    adwyz 2009-08-05 16:14:00

    一般来说我不会记得那么多种写法。可能是我对这个东西理解不深刻,如果某一种东西有很多种写法,一般来说我会记住最优秀的写法,忘记糟糕的写法。

  47. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-08-05 16:36:00

    adwyz:一般来说我不会记得那么多种写法。可能是我对这个东西理解不深刻,如果某一种东西有很多种写法,一般来说我会记住最优秀的写法,忘记糟糕的写法。


    没有糟糕不糟糕,只有合适不合适。

  48. TOOTH[未注册用户]
    *.*.*.*
    链接

    TOOTH[未注册用户] 2009-08-05 16:37:00

    我们谈的是面试的方式方法,而不是delegate的用法.好像回的不是贴是寂寞一样。
    我也算是从1.0到3.0逐渐瞎学了四五年的,所以也大概知道声明式-匿名&使用上下文变量-Lambda,不过您面试的时候如果问我这个问题,我可能一下子说上来,也可能一下子说不上来,也许我想一想才能说上来。所以在实际的项目中,会灵活选择方式使用,也许一时兴起会给新的程序员讲解一下它们的演进与变化,但是在面试的那几分钟,你突然问这个问题,却仍然可能真说不上来。然后面试官们一副鄙弃的心态(这也说不上来也算高级程序员?),最终还是没有找到解决面试寻找真正合适人的方法。
    偶也准备在找工作,目前尚在HW的某个部门,让我们从C#转向Java,哈哈,楼主要鄙弃一下了。所以不赞成玩这些文字游戏,还是想方法怎么样才能真正的互相了解。也许一个高级程序员他平常写的程序也和大家没有区别,但一年总有两三次能准确的指出项目组技术的方向或者风险,不也是挺好,有必要要求每一个细节吗?大家还是多讨论一下怎么样面试与招聘吧。

  49. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-08-05 16:38:00

    就像循环,有for,foreach,while,do while,goto……
    哪种是糟糕的写法?

  50. 老赵
    admin
    链接

    老赵 2009-08-05 16:42:00

    @TOOTH
    现在就在解释“为什么面试时可以问delegate”啊,当然下篇会讨论更多面试的,呵呵。我也相信,如果是你能够在平时给新程序员将一下演进和变化,面试时我想一定答的上来,因为我也不会要求立即回答,也是可以思考的,也是会给出一定的引导和提示的。
    我觉得,如果真是“能准确的指出项目组技术的方向或者风险”的高级程序员是不会不知道这个的。因为这是日常不断在用的东西,如果不会这个,给我的感觉就是一个C#程序员不会使用for,这如何能让人觉得这是个高级开发人员呢?

  51. billlo[未注册用户]
    *.*.*.*
    链接

    billlo[未注册用户] 2009-08-05 16:48:00

    說實話delegate 1.x的寫法卻實不記得了.
    另外從老趙文章得到了啟發.:D

  52. wrafe
    *.*.*.*
    链接

    wrafe 2009-08-05 16:58:00

    个人认为:
    初级学者编程:class+class
    程序员:class+interface+class
    像委托,事件这些从应用方面来讲已经到了高级阶段,一般有组件开发,自动工厂等开发的人比较了解,当然我讲的是引用不是写法和概念.

  53. cnbloger
    *.*.*.*
    链接

    cnbloger 2009-08-05 17:46:00

    赵兄
    问个问题
    为什么我在vs中看到的类String或是结构DateTime都是只有一些方法的声明却没有实现啊?
    本来我想看一下DateTime中的运算符重载是怎么写的。但只能看到声明。

  54. 老赵
    admin
    链接

    老赵 2009-08-05 18:14:00

    @cnbloger
    用.NET Reflector看。

  55. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-05 18:45:00

    有点意思
    在。net2里面,如果委托方法较短,一般都这样写
    AA.DelegateA = (MethodInvoker)new delegate(string msg)
    {
    ///几行处理代码
    };

    这种代码我写了很多,但是面试问我的话,我还真不一定说的出来,因为根本没去记过这个东西
    我写。net2年多了,也可能是我没写过。net1.1的缘故
    楼主的公司不错啊,HP
    啥时候在广州招人不,我也去试试水平

  56. 老赵
    admin
    链接

    老赵 2009-08-05 18:52:00

    @yeml
    原来还可以这么写啊,呵呵。不过既然有类型推断,为什么还要强制Cast成MethodInvoker呢?直接这样就好了:
    AA.DelegateA = delegate(string msg)
    {
    //...
    }

  57. Collector
    *.*.*.*
    链接

    Collector 2009-08-05 20:34:00

    听过建忠老师的webcast的锐利系列,基本知道匿名和Lambda是由编译器在背后做了很多工作,最终生成的IL和直接用delegate是一样的。
    但第一次看到有人把匿名用得这么妙啊。
    非常感谢!

  58. CoolCode
    *.*.*.*
    链接

    CoolCode 2009-08-05 20:45:00

    Jeffrey Zhao:
    老赵平时的工作之一,便是为项目提供各种扩展API,可以让程序员们更愉快地进行开发工作
    }


    哈哈~我也喜欢这样,估计我以后会跟你走差不多的路~自我陶醉ing

  59. Richet
    *.*.*.*
    链接

    Richet 2009-08-05 20:59:00

    非常有收获,经常用,但是概念嘛,还是有点模糊的
    在关于最近面试的一点感想 提到了
    delegate->MulticastDelegate->Delegate层次关系

    能不能给一个解释呢?

  60. 老赵
    admin
    链接

    老赵 2009-08-05 22:04:00

    @Richet
    这个我不太关心,我认为不重要,需要知道的话,看一下CLR via C#就可以了。

  61. 钧梓昊逑
    *.*.*.*
    链接

    钧梓昊逑 2009-08-05 22:05:00

    Richet:
    非常有收获,经常用,但是概念嘛,还是有点模糊的
    在关于最近面试的一点感想 提到了
    delegate->MulticastDelegate->Delegate层次关系

    能不能给一个解释呢?



    delegate在编译后会展开一个继承自MulticastDelegate的类
    MulticastDelegate继承自Delegate
    就是这个意思

  62. 老赵
    admin
    链接

    老赵 2009-08-05 22:16:00

    @钧梓昊逑
    说实话我觉得这个真没啥意义,知道了也用不着,不知道也不会错。

  63. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-05 22:23:00

    @Jeffrey Zhao

    Jeffrey Zhao:
    @yeml
    原来还可以这么写啊,呵呵。不过既然有类型推断,为什么还要强制Cast成MethodInvoker呢?直接这样就好了:
    AA.DelegateA = delegate(string msg)
    {
    //...
    }


    .net 2.0没有类型推断
    我不用3.5,那个framework太大了,受不了

  64. 老赵
    admin
    链接

    老赵 2009-08-05 22:27:00

    @yeml
    我说的是delegate类型,不是参数类型。
    C# 2.0可以自动推断delegate类型,C# 3.0才会推断参数类型。

  65. Kain
    *.*.*.*
    链接

    Kain 2009-08-05 22:30:00

    大道至简,越是简单的东西用好越是不容易。

  66. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-05 22:32:00

    补充一下,中午在公司的时候,随记忆写的代码,所以不正确
    应该是这样的
    private void UpdateImage()
    {
    Bitmap map = this.OnGetValidCodeImage();
    this.Invoke((MethodInvoker)delegate()
    {
    this.pictureBox1.Image = map;
    });
    }
    MSDN的说明:
    MethodInvoker 提供一个简单委托,该委托用于调用含 void 参数列表的方法。在对控件的 Invoke 方法进行调用时或需要一个简单委托又不想自己定义时可以使用该委托。

    主要是用在空间的Invoke方法里面,范围比较局限,不过在写winform的时候很好用
    他的定义是这样的:public delegate void MethodInvoker ()

    很多东西我都比较熟,但是没像楼主你这样仔细的去甄别,向你学习

  67. flyingchen
    *.*.*.*
    链接

    flyingchen 2009-08-05 22:33:00

    java相比确实差多了,写的头疼

  68. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-05 22:36:00

    flyingchen:java相比确实差多了,写的头疼


    java的其实也没差那么远,就是要多定义一个接口而已。

  69. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-05 22:40:00

    Jeffrey Zhao:
    @yeml
    原来还可以这么写啊,呵呵。不过既然有类型推断,为什么还要强制Cast成MethodInvoker呢?直接这样就好了:
    AA.DelegateA = delegate(string msg)
    {
    //...
    }

    根据你后来的说明,你这个说法是对的。其实这个在。net2.0里面有个Action<T> 了,还有个有返回值的版本:Func<T,T>,看你的文章,你最喜欢用Action了。其实在Action之前,还有个EventHandler,这个的定义是这样的:delegate void EventHandler(),既无参数,又无返回值的函数抽象

  70. 老赵
    admin
    链接

    老赵 2009-08-05 23:04:00

    @yeml
    一是要定义接口(如Runnable),二是没有Lambda表达式,写一个内联的类太多代码。

  71. 老赵
    admin
    链接

    老赵 2009-08-05 23:05:00

    @yeml
    我喜欢用Action,因为通用,MethodInvoker和EventHandler都有“含义”在里面了,呵呵。
    // EventHandler为什么不是void EventHandler(object, EventArgs)类型的啊?

  72. 一抹红
    *.*.*.*
    链接

    一抹红 2009-08-05 23:07:00

    期待下篇介绍的lambda表达式妙用啊

  73. 老赵
    admin
    链接

    老赵 2009-08-05 23:12:00

    @一抹红
    没有妙用,全是普通应用,再阐述一下优势,最后讨论讨论。

  74. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-05 23:26:00

    Jeffrey Zhao:
    @yeml
    我喜欢用Action,因为通用,MethodInvoker和EventHandler都有“含义”在里面了,呵呵。
    // EventHandler为什么不是void EventHandler(object, EventArgs)类型的啊?


    嗯嗯,是void EventHandler(object, EventArgs)

  75. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-05 23:30:00

    讨论技术有点意思

    我正在弄一个问题:SoapClient 访问SoapService
    传递的参数实现了IDictionary,所以无法Xml 序列化。所以我之前把这个对象转成Base64 string,不过后来出这样的问题:ERROR 2009-08-05 23:10:26,250 [9] BasePluginForm - 异常信息:System.Web.Services.Protocols.SoapHeaderException: Server unavailable, please try later ---> System.InvalidOperationException: XML 文档(1, 164)中有错误。 ---> System.Xml.XmlException: “.”(十六进制值 0x00)是无效的字符。 行 1,位置 164。

    不知道jeff zhao碰到过没有。

  76. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-05 23:32:00

    用EventHandler可以省掉一些delegate的定义,但是因为方法的参数类型级别太高,需要在代码内做类型转换,而且调用的时候无法保证类型安全。但是如果一个人的项目,用这个还是蛮好的。

  77. 老赵
    admin
    链接

    老赵 2009-08-05 23:35:00

    @yeml
    碰到过,由于某些原因,我还把它叫做“90后脑残体”问题。
    解决方法就是过滤掉这些字符了,不过对你来说可能不行……
    // 不过BASE64 String怎么会有这个错误?

  78. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-05 23:41:00

    是啊,写这个功能之前就想到这些问题了,特意转成Base64传过去的,没像还是跑不掉。。。
    正打算下个nunit慢慢调。。。

  79. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-05 23:44:00

    哈哈,说起Nunit,就想起微软Vs里面那个单元测试工具,生成巨大的临时文件,真是莫名其妙。

  80. 老赵
    admin
    链接

    老赵 2009-08-06 00:08:00

    @yeml
    是大量的test report吧,每执行一次都会多一个出来的。

  81. yeml[未注册用户]
    *.*.*.*
    链接

    yeml[未注册用户] 2009-08-06 00:12:00

    @Jeffrey Zhao
    是的,report,不删掉会导致下次测试的时候巨慢,好像是这样。

  82. Jeffrey Chan
    *.*.*.*
    链接

    Jeffrey Chan 2009-08-06 09:16:00

    似乎那位不小心把变量锁错了的同学就是鄙人或。汗颜或。

  83. 某位同学[未注册用户]
    *.*.*.*
    链接

    某位同学[未注册用户] 2009-08-06 09:22:00

    咱,咱是出来打酱油滴……

  84. 老赵
    admin
    链接

    老赵 2009-08-06 09:49:00

    @Jeffrey Chan
    你的毛病还是没改啊,因为我说的可不是变量锁错了……

  85. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-06 10:22:00

    Jeffrey Zhao:
    老赵平时的工作之一,便是为项目提供各种扩展API,可以让程序员们更愉快地进行开发工作
    }
    -------------------------------------------------
    这种工作太棒了,只专注Framework,不理会烦人的业务逻辑,我也喜欢

  86. 老赵
    admin
    链接

    老赵 2009-08-06 10:25:00

    @麒麟.NET
    我觉得正好相反,你需要从烦人的业务逻辑里提取出可以简化的基础部分,进行抽象,让原本烦人的业务逻辑对于其他开发人员来说变得相对简单。
    通俗地讲就是为一个项目“搭架子”,很头疼的。

  87. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-08-06 10:35:00

    从不断重复的逻辑中寻找模式

  88. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-08-06 10:38:00

    老赵
    这个标题太散文了
    谈开去 就是谈到界外了
    改叫漫谈比亲切阿

  89. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-06 10:40:00

    Jeffrey Zhao:
    @麒麟.NET
    我觉得正好相反,你需要从烦人的业务逻辑里提取出可以简化的基础部分,进行抽象,让原本烦人的业务逻辑对于其他开发人员来说变得相对简单。
    通俗地讲就是为一个项目“搭架子”,很头疼的。


    仔细想想,确实是这样,似乎是对业务逻辑理解得很透彻了,才能搭好这种架子。
    什么时候能把这些API公开一下啊?

  90. 老赵
    admin
    链接

    老赵 2009-08-06 10:47:00

    韦恩卑鄙:
    老赵
    这个标题太散文了
    谈开去 就是谈到界外了
    改叫漫谈比亲切阿


    “漫谈”我理解就是“松散地谈,随性地谈”,“delegate漫谈”的意思是“随性地谈delegate”但是还是谈delegate。
    下篇我其实还打算谈谈面试啊,谈谈对待技术的态度等等,所以我用“谈开去”,呵呵。

  91. 老赵
    admin
    链接

    老赵 2009-08-06 10:48:00

    @麒麟.NET
    一些基础API会有的吧,不过的确是很基础的API。

  92. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-08-06 11:01:00

    Jeffrey Zhao:

    韦恩卑鄙:
    老赵
    这个标题太散文了
    谈开去 就是谈到界外了
    改叫漫谈比亲切阿


    “漫谈”我理解就是“松散地谈,随性地谈”,“delegate漫谈”的意思是“随性地谈delegate”但是还是谈delegate。
    下篇我其实还打算谈谈面试啊,谈谈对待技术的态度等等,所以我用“谈开去”,呵呵。



    果然不是散文 形散深更散

  93. 懒得登陆了[未注册用户]
    *.*.*.*
    链接

    懒得登陆了[未注册用户] 2009-08-06 12:07:00

    老赵再写一篇为什么要这么改进吧 ?底层没有变化吗?

  94. oraclesun[未注册用户]
    *.*.*.*
    链接

    oraclesun[未注册用户] 2009-08-06 13:03:00

    写没有深度的东西充大师,可以不?精神胜利法挺能激励人的。

  95. 老赵
    admin
    链接

    老赵 2009-08-06 14:45:00

    懒得登陆了:老赵再写一篇为什么要这么改进吧 ?底层没有变化吗?


    底层没有任何变化,一切只是“文本表现”上的改变。
    至于为什么这么改进,其实就是这些改进的“好处”,这些已经写过了。

  96. 老赵
    admin
    链接

    老赵 2009-08-06 14:59:00

    韦恩卑鄙:果然不是散文 形散深更散


    应该是神散形不散,嗯嗯。

  97. 伊牛娃
    *.*.*.*
    链接

    伊牛娃 2009-08-06 17:00:00

    崇拜老赵

  98. 菜鸟毛
    *.*.*.*
    链接

    菜鸟毛 2009-08-06 17:52:00

    老赵,"从.NET框架中委托写法的演变谈开去(下)"是个#号,是咋回事啊.

    您这一篇又够我啃一星期了

  99. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-08-06 17:53:00

    吐槽

    神散形不散
    就是披着技术讨论的外衣 走着逻辑清晰的道路 说一些云山雾罩不三不四没溜的话

  100. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-08-06 17:54:00

    request.BeginGetResponse(ar => TestAsyncCallback(ar, request, url), null);

    就这一句 就足以让vb 程序员泪流满面了

    nnd vb 凭什么不让用void 的lambda阿

  101. 老赵
    admin
    链接

    老赵 2009-08-06 17:59:00

    @韦恩卑鄙
    对于Lambda来说,VB里的Function就像JavaScript里的function一样令人厌烦……

  102. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-08-06 18:00:00

    能像js用我就忍了
    可惜啊

  103. 老赵
    admin
    链接

    老赵 2009-08-06 18:00:00

    菜鸟毛:
    老赵,"从.NET框架中委托写法的演变谈开去(下)"是个#号,是咋回事啊.


    别急,还没写,占为用的。
    而且我决定拆成上中下三篇了,因为刚把“中”写完,内容已经很够意思了。

  104. 腻革命儿[未注册用户]
    *.*.*.*
    链接

    腻革命儿[未注册用户] 2009-08-06 19:28:00

    好酸的开头,好愤青的卫道士。肚里一半的学问怕是还有一半的怨气吧,要不此文写的应能更快些。世上有铁才有钢,老赵又何必一副瞧不起铁皮的金刚像呢?老赵老赵的,忘了劼啥意思了,嘿嘿。

  105. 老赵
    admin
    链接

    老赵 2009-08-06 20:39:00

    @腻革命儿
    是啊,被你看出来的,在后面的文章里我会抱怨更多呢。

  106. Jeffrey Chan
    *.*.*.*
    链接

    Jeffrey Chan 2009-08-06 21:46:00

    @Jeffrey Zhao
    赵帅,改了许多了。遇到问题都是先思考,再寻求解决方法,而不是一味的去求助别人。只是能力有限,每回总是第一时间看到的是现象,不是本质。这个只有慢慢进化了。可惜基础编程能力似乎只提高了一点点,可悲。

  107. blo[未注册用户]
    *.*.*.*
    链接

    blo[未注册用户] 2009-08-07 08:05:00

    下篇应该就是Lambda和ExpressionTree了吧?

    其实相对要求candidate知道delegate有多少种写法,我更喜欢他能讲明白delegate到底是什么。至少2.0,3.5中那些语法糖,合格的程序员简单msdn一下就能明白吧,如果他恰巧又懂那么一点functional programming,那有些东西理解起来就更简单了

  108. 老赵
    admin
    链接

    老赵 2009-08-07 08:34:00

    @blo
    只有委托,没有Expression Tree。
    如果知道delegate到底是什么,如果是看看msdn就能明白的话,我不信他就不知道delegate可以怎么写。
    事实上,如果是了解函数式编程的C#程序员,他肯定知道Lambda表达式,而且知道Lambda表达式好处的人,一定知道以前是怎么写的,因为“有对比才有差距”。
    我觉得高级内容和初级内容不能分开对待,认为初级内容很快就能学会,所以不用关心。但是我就是不相信,一个了解高级内容的人,会不知道初级内容该怎么搞。

  109. 鹤冲天
    *.*.*.*
    链接

    鹤冲天 2009-08-08 19:08:00

    委托、泛型、扩展方法等,感觉像一场c#的革命!
    会用的享受带来的便利...
    是否已经成为“高手”和“新手”的分界线了...

  110. 勇赴
    *.*.*.*
    链接

    勇赴 2009-08-10 16:47:00

    @Jeffrey Zhao
    我感觉也是,看视频很多时候犯困,要是有完备的文档,自己慢慢消化比较好

  111. 多大点事儿[未注册用户]
    *.*.*.*
    链接

    多大点事儿[未注册用户] 2009-08-10 17:09:00

    上个星期看了委托和事件,这个星期学习委托和匿名方法,第一次到老赵这里,真是个夏天避暑学习的好地方啊

  112. 老赵
    admin
    链接

    老赵 2009-08-10 18:27:00

    @多大点事儿
    初学者如果可以很快了解这个,我觉得很厉害啊。

  113. 郑希强
    *.*.*.*
    链接

    郑希强 2009-08-12 14:14:00

    看的好吃力

  114. 非空
    *.*.*.*
    链接

    非空 2009-08-20 13:56:00

    private Lazy<BigInstance> m_lazyInstance =
    new Lazy<BigInstance>(() => new BigInstance());
    public BigInstance Instance { get { return this.m_lazyInstance.Value; } }
    这段代码不是放在BigInstance类里边的吧?

  115. 老赵
    admin
    链接

    老赵 2009-08-20 13:58:00

    @非空
    其实只是个代码片断。
    估计加上static更有意义一些,就是个singleton模式了。

  116. 非空
    *.*.*.*
    链接

    非空 2009-08-20 15:12:00

  117. 老赵
    admin
    链接

    老赵 2009-08-20 15:22:00

    @非空
    呵呵,话说它类似的容器很有用的,FastReflectionLib的AccessorCache就是得,我刚写的AreaControllerFactory也有类似的逻辑。

  118. Will Meng
    *.*.*.*
    链接

    Will Meng 2009-12-01 09:31:00

    @Jeffrey Zhao

    既然是要抽象出来共通的东西,那么这样做有何不可呢?

    public static T GetMyObject<T>()
    {
        object m_mutex = new object();
        bool m_initialized = false;
        T m_instance = default(T);
    
        if (!m_initialized)
        {
            lock (m_mutex)
            {
                if (!m_initialized)
                {
                    m_instance = Activator.CreateInstance<T>();
                    m_initialized = true;
                }
            }
        }
    
        return m_instance;
    }
    

    不好意思,才看到您以前的文章。。。

  119. 老赵
    admin
    链接

    老赵 2009-12-01 09:34:00

    @Will Meng
    这么做当然不行……这个很基础的,自己想象吧……

  120. Will Meng
    *.*.*.*
    链接

    Will Meng 2009-12-01 11:06:00

    @Jeffrey Zhao
    仔细看了看,没看出来为什么不行。
    难道是每次调用该静态方法都会创建一个相同的对象?
    不知道了。。。

  121. 老赵
    admin
    链接

    老赵 2009-12-01 11:10:00

    @Will Meng
    真的是基础,再看看吧。

  122. Will Meng
    *.*.*.*
    链接

    Will Meng 2009-12-01 13:02:00

    @Jeffrey Zhao
    对于那些只需要“按需创建”,且要“线程安全”的对象.
    这是您的需求,我从这两个方面对我的方法进行了验证。没发现什么异常啊。

  123. 老赵
    admin
    链接

    老赵 2009-12-01 13:07:00

    @Will Meng
    那就别用看的,运行一下,看看两次返回的是不是同一个对象。

  124. Will Meng
    *.*.*.*
    链接

    Will Meng 2009-12-01 13:49:00

    @Jeffrey Zhao

    首先,声明我一直是通过运行和调试代码来测试结果的。我不知道您的“看看两次返回的是不是同一个对象”是什么意思。我用我的方法和您的方法(就是后来抽象出来的那个),测试了一下,没有发现什么异同。

    代码如下:

    Thread t1 = new Thread(delegate()
    {
        GetA();
    });
    
    Thread t2 = new Thread(delegate()
    {
        GetA();
    });
    
    Thread t3 = new Thread(() => GetAByAnonmous());
    Thread t4 = new Thread(() => GetAByAnonmous());
    
    static void GetA()
    {
        A a1 = GetObject.GetMyObject<A>();
        Console.WriteLine("Current Thread : " + Thread.CurrentThread.ManagedThreadId.ToString());
        Console.WriteLine("A1 GetHashCode : " + a1.GetHashCode().ToString());
        Console.WriteLine("A1 Type : " + a1.GetType().ToString());
        Console.WriteLine("-------------------------------------------------");
    }
    
    static void GetAByAnonmous()
    {
        A a2 = new Lazy<A>(() => new A()).Value;
        Console.WriteLine("Current Thread : " + Thread.CurrentThread.ManagedThreadId.ToString());
        Console.WriteLine("A2 GetHashCode : " + a2.GetHashCode().ToString());
        Console.WriteLine("A2 Type : " + a2.GetType().ToString());
        Console.WriteLine("-------------------------------------------------");
    }
    

    测试结果如下:

    结果

  125. 老赵
    admin
    链接

    老赵 2009-12-01 14:00:00

    @Will Meng
    结果没有差别是因为你把Lazy<T>用错了。
    我给你一段代码(你贴代码为啥总是不注意缩进……):

    var a1 = GetObject.GetMyObject<A>();
    var a2 = GetObject.GetMyObject<A>();
    Console.WriteLine(a1 == a2);
    
    var lazy = new Lazy<A>(() => new A());
    var b1 = lazy.Value;
    var b2 = lazy.Value;
    Console.Write(b1 == b2);

    其实从你写的代码和你对Lazy<T>对象的用法上,我都觉得其实你没有搞清楚一些最基本的东西……

  126. 以吾之名
    *.*.*.*
    链接

    以吾之名 2009-12-07 01:29:00

    “请问楼主,茴香豆的茴有几种写法”
    “茴”字的意义也一直没有变,变的只是写法

  127. 老张
    116.236.185.*
    链接

    老张 2010-05-13 14:41:25

    public static void TestRequest(string url)
    {
        WebRequest request = HttpWebRequest.Create(url);
        object[] context = new object[] { url, request };
        request.BeginGetResponse(TestAsyncCallback, context);
    }
    
    public static void TestAsyncCallback(IAsyncResult ar)
    { 
        object[] context = (object[])ar.AsyncState;
        string url = (string)context[0];
        WebRequest request = (WebRequest)context[1];
    
        using (WebResponse response = request.EndGetResponse(ar))
        {
            Console.WriteLine("{0}: {1}", url, response.ContentLength);
        }
    }
    

    中request.BeginGetResponse(TestAsyncCallback, context); 是否漏掉一个函数(TestAsyncCallback)参数;

  128. 链接

    2011-09-08 15:39:24

    赵老师能否给点练手的小题目,多谢了:)

  129. 链接

    2011-09-08 23:30:56

    赵老师,请教一您一下,我在学习过程中遇到了以下的问题,能否帮忙解释一下:

    private Lazy<BigInstance> mLazyInstance = new Lazy<BigInstance>
    (
        // delegate
        // { return new BigInstance(); }
    
        // 用下面的语句替换上面的注释部分,编译通不过,
        // 错误:2 并非所有代码路径都返回类型
        // 为“System.Func<WindowsFormsApplication2.BigInstance>”的 匿名方法 中的值    
        delegate { ReturnBigIT(); }
    );
    
    //其中ReturnBigIT定义如下:
    private static BigInstance ReturnBigIT() 
    {
        return new BigInstance();
    }
    

    我感觉这两句话干的事情是一样的啊。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我