Hello World
Spiga

从.NET中委托写法的演变谈开去(中):Lambda表达式及其优势

2009-08-07 08:24 by 老赵, 17900 visits

上一篇文章中我们简单探讨了.NET 1.x和.NET 2.0中委托表现形式的变化,以及.NET 2.0中匿名方法的优势、目的及注意事项。那么现在我们来谈一下.NET 3.5(C# 3.0)中,委托的表现形式又演变成了什么样子,还有什么特点和作用。

.NET 3.5中委托的写法(Lambda表达式)

Lambda表达式在C#中的写法是“arg-list => expr-body”,“=>”符号左边为表达式的参数列表,右边则是表达式体(body)。参数列表可以包含0到多个参数,参数之间使用逗号分割。例如,以下便是一个使用Lambda表达式定义了委托的示例1

Func<int, int, int> max = (int a, int b) =>
{
    if (a > b)
    {
        return a;
    }
    else
    {
        return b;
    }
};

与上文使用delegate定义匿名方法的作用相同,Lambda表达式的作用也是为了定义一个匿名方法。因此,下面使用delegate的代码和上面是等价的:

Func<int, int, int> max = delegate(int a, int b)
{
    if (a > b)
    {
        return a;
    }
    else
    {
        return b;
    }
};

那么您可能就会问,这样看来Lambda表达式又有什么意义呢?Lambda表达式的意义便是它可以写的非常简单,例如之前的Lambda表达式可以简写成这样:

Func<int, int, int> max = (a, b) =>
{
    if (a > b)
    {
        return a;
    }
    else
    {
        return b;
    }
};

由于我们已经注明max的类型是Func<int, int, int>,因此C#编译器可以明确地知道a和b都是int类型,于是我们就可以省下参数之前的类型信息。这个特性叫做“类型推演”,也就是指编译器可以自动知道某些成员的类型2。请不要轻易认为这个小小的改进意义不大,事实上,您会发现Lambda表达式的优势都是由这一点一滴的细节构成的。那么我们再来一次改变:

Func<int, int, int> max = (a, b) => a > b ? a : b;

如果Lambda表达式的body是一个表达式(expression),而不是语句(statement)的话,那么它的body就可以省略大括号和return关键字。此外,如果Lambda表达式只包含一个参数的话,则参数列表的括号也可以省略,如下:

Func<int, bool> positive = a => a > 0;

如今的写法是不是非常简单?那么我们来看看,如果是使用delegate关键字来创建的话会成为什么样子:

Func<int, bool> positive = delegate(int a)
{
    return a > 0;
};

您马上就可以意识到,这一行和多行的区别,这几个关键字和括号的省略,会使得编程世界一下子变得大为不同。

当然,Lambda表达式也并不是可以完全替代delegate写法,例如带ref和out关键字的匿名方法,就必须使用.NET 2.0中的delegate才能构造出来了。

使用示例一

Lambda表达式的增强在于“语义”二字。“语义”是指代码所表现出来的含义,说的更通俗一些,便是指一段代码给阅读者的“感觉”如何。为了说明这个例子,我们还是使用示例来说明问题。

第一个例子是这样的:“请写一个方法,输入一个表示整型的字符串列表,并返回一个列表,包含其中偶数的平方,并且需要按照平方后的结果排序”。很简单,不是吗?相信您一定可以一蹴而就:

static List<int> GetSquaresOfPositive(List<string> strList)
{
    List<int> intList = new List<int>();
    foreach (var s in strList) intList.Add(Int32.Parse(s));

    List<int> evenList = new List<int>();
    foreach (int i in intList)
    {
        if (i % 2 == 0) evenList.Add(i);
    }

    List<int> squareList = new List<int>();
    foreach (int i in evenList) squareList.Add(i * i);

    squareList.Sort();
    return squareList;
}

我想问一下,这段代码给您的感觉是什么?它给我的感觉是:做了很多事情。有哪些呢?

  1. 新建一个整数列表intList,把参数strList中所有元素转化为整型保存起来。
  2. 新建一个整数列表evenList,把intList中的偶数保存起来。
  3. 新建一个整数列表squareList,把evenList中所有数字的平方保存起来。
  4. 将squareList排序。
  5. 返回squareList。

您可能会问:“当然如此,还能怎么样?”。事实上,如果使用了Lambda表达式,代码就简单多了:

static List<int> GetSquaresOfPositiveByLambda(List<string> strList)
{
    return strList
        .Select(s => Int32.Parse(s)) // 转成整数
        .Where(i => i % 2 == 0) // 找出所有偶数
        .Select(i => i * i) // 算出每个数的平方
        .OrderBy(i => i) // 按照元素自身排序
        .ToList(); // 构造一个List
}

配合.NET 3.5中定义的扩展方法,这段代码可谓“一气呵成”(在实际编码过程中,老赵更倾向于把这种简短的“递进式”代码写作一行)。那么这行代码的“语义”又有什么变化呢?在这里,“语义”的变化在于代码的关注点从“怎么做”变成了“做什么”。这就是Lambda表达式的优势。

在第一个方法中,我们构造了多个容器,然后做一些转化,过滤,并且向容器填充内容。其实这些都是“怎么做”,也就是所谓的“how (to do)”。但是这些代码并不能直接表示我们想要做的事情,我们想要做的事情其实是“得到XXX”,“筛选出YYY”,而不是“创建容器”,“添加元素”等操作。

在使用Lambda表达式的实现中,代码变得“声明式(declarative)”了许多。所谓“声明式”,便是“声称代码在做什么”,而不像“命令式(imperative)”的代码在“操作代码怎么做”。换句话说,“声明式”关注的是“做什么”,是指“what (to do)”。上面这段声明式的代码,其语义则变成了:

  1. 把字符串转化为整数
  2. 筛选出所有偶数
  3. 把每个偶数平方一下
  4. 按照平方结果自身排序 
  5. 生成一个列表

至于其中具体是怎么实现的,有没有构造新的容器,又是怎么向容器里添加元素的……这些细节,使用Lambda表达式的代码一概不会关心——这又不是我们想要做的事情,为什么要关心它呢?

虽然扩展方法功不可没,但我认为,Lambda表达式在这里的重要程度尤胜前者,因为它负责了最关键的“语义”。试想,“i => i * i”给您的感觉是什么呢?是构造了一个委托吗(当然,您一定知道在这里其实构造了一个匿名方法)?至少对我来说,它的含义是“把i变成i * i”;同样,“i => i % 2 == 0”给我的感觉是“(筛选标准为)i模2等于零”,而不是“构造一个委托,XXX时返回true,否则返回false”;更有趣的是,OrderBy(i => i)给我的感觉是“把i按照i自身排序”,而不是“一个返回i自身的委托”。这一切,都是在“声明”这段代码在“做什么”,而不是“怎么做”。

没错,“类型推演”,“省略括号”和“省略return关键字”可能的确都是些“细小”的功能,但也正是这些细微之处带来了编码方式上的关键性改变。

使用示例二

使用Lambda表达式还可以节省许多代码(相信您从第一个示例中也可以看出来了)。不过我认为,最省代码的部分更应该可能是其“分组”和“字典转化”等功能。因此,我们来看第二个示例。

这个示例可能更加贴近现实。不知您是否关注过某些书籍后面的“索引”,它其实就是“列出所有的关键字,根据其首字母进行分组,并且要求对每组内部的关键字进行排序”。简单说来,我们需要的其实是这么一个方法:

static Dictionary<char, List<string>> GetIndex(IEnumerable<string> keywords) { ... }

想想看,您会怎么做?其实不难(作为示例,我们这里只关注小写英文,也不关心重复关键字这种特殊情况):

static Dictionary<char, List<string>> GetIndex(IEnumerable<string> keywords)
{
    // 定义字典
    var result = new Dictionary<char, List<string>>();

    // 填充字典
    foreach (var kw in keywords)
    {
        var firstChar = kw[0];
        List<string> groupKeywords;

        if (!result.TryGetValue(firstChar, out groupKeywords))
        {
            groupKeywords = new List<string>();
            result.Add(firstChar, groupKeywords);
        }

        groupKeywords.Add(kw);
    }

    // 为每个分组排序
    foreach (var groupKeywords in result.Values)
    {
        groupKeywords.Sort();
    }

    return result;
}

那么如果利用Lambda表达式及.NET框架中定义的扩展方法,代码又会变成什么样呢?请看:

static Dictionary<char, List<string>> GetIndexByLambda(IEnumerable<string> keywords)
{
    return keywords
        .GroupBy(k => k[0]) // 按照首字母分组
        .ToDictionary( // 构造字典
            g => g.Key, // 以每组的Key作为键
            g => g.OrderBy(k => k).ToList()); // 对每组排序并生成列表
}

光从代码数量上来看,前者便是后者的好几倍。而有关“声明式”,“what”等可读性方面的优势就不再重复了,个人认为它比上一个例子给人的“震撼”有过之而无不及。

试想,如果我们把GetIndexByLambda方法中的Lambda表达式改成.NET 2.0中delegate形式的写法:

static Dictionary<char, List<string>> GetIndexByDelegate(IEnumerable<string> keywords)
{
    return keywords
        .GroupBy(delegate(string k) { return k[0]; })
        .ToDictionary(
            delegate(IGrouping<char, string> g) { return g.Key; },
            delegate(IGrouping<char, string> g)
            {
                return g.OrderBy(delegate(string s) { return s; }).ToList();
            });
}

您愿意编写这样的代码吗?

因此,Lambda表达式在这里还是起着决定性的作用。事实上正是因为有了Lambda表达式,.NET中的一些函数式编程特性才被真正推广开来。“语言特性”决定“编程方式”的确非常有道理。这一点上Java是一个很好的反例:从理论上说,Java也有“内联”的写法,但是C#的使用快感在Java那边还只能是个梦。试想GetIndexByLambda在Java中会是什么情况3

public Dictionary<Char, List<String>> GetIndexInJava(Enumerable<String> keywords)
{
    return keywords
        .GroupBy(
            new Func<String, Char> {
                public Char execute(String s) { return s.charAt(0); }
            })
        .ToDictionary(
            new Func<Grouping<Char, String>, Char> {
                public Char execute(IGrouping<Char, String> g) { return g.getKey(); }
            },
            new Func<Grouping<Char, String>, List<string>> {
                public List<String> execute(IGrouping<Char, String> g)
                {
                    return g
                        .OrderBy(
                            new Func<String, String> {
                                public String execute(String s) { return s; }
                            })
                        .ToList();
                }
            });
}

一股语法噪音的气息扑面而来,让人无法抵挡。由于Java中的匿名类型语法(即上面这种内联写法)连类型信息(new Func<String, Char>{ ... }这样的代码)都无法省去,因此给人非常繁琐的感觉。面对这样的代码,您可能会有和我一样的想法:“还不如最普通的写法啊”。没错,这种函数式编程的风格,由于缺乏语言特性支持,实在不适合在Java语言中使用。事实上,这种内联写法很早就出现了(至少在02、03年我还在使用Java的时候就已经有了),但是那么多年下来一点改进都没有。而Lambda表达式出现之后,社区中立即跟进了大量项目,如MoqFluent NHibernate等等,充分运用了C# 3.0的这一新特性。难道这还不够说明问题吗?

对了,再次推荐一下Scala语言,它的代码可以写的和C#一样漂亮。我不是Java平台的粉丝,更是Java语言的忠实反对者,但是我对Java平台上的Scala语言和开源项目都抱有强烈的好感。

既然谈到了函数式编程,那么就顺便再多说几句。其实这两个例子都有浓厚的函数式编程影子在里面,例如,对于函数试编程来说,Where常被叫做filter,Select常被叫做map。而.NET 3.5中定义的另一些方法在函数式编程里都有体现(如Aggregate相当于fold)。如果您对这方面感兴趣,可以关注Matthew Poswysocki提出的Functional C#类库。

总结

既可以提高可读性,又能够减少代码数量,我实在找不出任何理由拒绝Lambda表达式。

哦,对了,您可能会提到“性能”,这的确也是一个重要的方面,不过关于这个话题我们下次再谈。受篇幅限制,原本计划的“上”“下”两篇这次又不得不拆开了。至于其他的内容,也等讨论完性能问题之后再说吧。

当然,世界上没有东西是完美的,如果您觉得Lambda表达式在某些时候会给您带来“危害”,那么也不妨使用delegate代替Lambda表达式。例如,为了代码清晰,在某些时候还是显式地指明参数类型比较好。不过对我而言,在任何情况下我都会使用Lambda表达式——最多使用“(int a, string b) =>”的形式咯,我想总比“delegate(int a, string b)”要统一、省事一些吧。

相关文章

 

注1:严格说来,这里的body是一个“语句(statement)”,而不是“表达式(expression)”。因为一个委托其实是一个方法,因此使用Lambda来表示一个委托,其中必然要包含“语句”。不过在目前的C#中,Lambda表达式还有一个作用是构造一颗“表达式树”,而目前的C#编译器只能构造“表达式树”而不是“语句树”。

注2:事实上,在.NET 2.0使用delegate关键字定义匿名方法时已经可以有些许“类型推演”的意味了——虽然还是必须写明参数的类型,但是我们已经可以省略委托的类型了,不是吗?

注3:除非我们补充Func、Enumerable,Dictionary,Grouping等类型及API,否则这段代码在Java中是无法编译通过的。事实上,这段Java代码是我在记事本中写出来的。不过这个形式完全正确。

Creative Commons License

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

Add your comment

120 条回复

  1. Gnie
    *.*.*.*
    链接

    Gnie 2009-08-07 08:26:00

    先前排占上

  2. 麒麟
    *.*.*.*
    链接

    麒麟 2009-08-07 08:27:00

    沙发

  3. 日本麒麟[未注册用户]
    *.*.*.*
    链接

    日本麒麟[未注册用户] 2009-08-07 08:33:00

    垃圾,抄MSDN抄国外技术博客
    估计你自己都一知半解

  4. 张少峰
    *.*.*.*
    链接

    张少峰 2009-08-07 08:38:00

    ls,说别人抄袭请举出原文,否则可是污蔑啊~
    说老赵对Lambda一知半解,大家看到这句都笑了~

  5. 青羽
    *.*.*.*
    链接

    青羽 2009-08-07 08:45:00

    能介绍下Func吗?

  6. 老赵
    admin
    链接

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

    日本麒麟:
    垃圾,抄MSDN抄国外技术博客
    估计你自己都一知半解


    我的原则就是不翻译,外国人写过的我不写,没有思考的“文档货”我不写。
    说我抄袭简直是笑话,有本事你找出抄袭的依据来?果然是日本人。

  7. 老赵
    admin
    链接

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

    @青羽
    Func就是个翻型委托,具体定义看一下MSDN吧。

  8. Jimixu
    *.*.*.*
    链接

    Jimixu 2009-08-07 08:52:00

    那个没注册的,给你抄你抄个给我看看?骂人之前,先拿镜子照照自己,你有这能耐么?鲁班门前弄大斧,关公面前耍大刀!徒增笑尔~

  9. Jimixu
    *.*.*.*
    链接

    Jimixu 2009-08-07 08:55:00

    晕,我还想点“下”篇呢,点了半天还在“中”,原来还没写呢!伤心啊

  10. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-07 08:56:00

    又从两篇变三篇了啊……
    // 今天咋冒出来这么多麒麟?
    // 那个日本的是咋回事?

  11. 老赵
    admin
    链接

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

    @麒麟.NET
    上下 => 上中下 => 12345……
    我的不少文章是这样来的。
    那个Actor系列,原本打算“上中下”,现在变成8篇了。

  12. Leven
    *.*.*.*
    链接

    Leven 2009-08-07 09:04:00

    期待下....

  13. qianbao
    *.*.*.*
    链接

    qianbao 2009-08-07 09:04:00

    Jeffrey Zhao:

    日本麒麟:
    垃圾,抄MSDN抄国外技术博客
    估计你自己都一知半解


    我的原则就是不翻译,外国人写过的我不写,没有思考的“文档货”我不写。
    说我抄袭简直是笑话,有本事你找出抄袭的依据来?果然是日本人。




    支持老赵
    这个小日本太过分

  14. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-07 09:11:00

    嗯,确实语法的简化带来了语义的清晰。

  15. hhshuai
    *.*.*.*
    链接

    hhshuai 2009-08-07 09:15:00

    好文,讲得很清楚啊,我这半吊子都能看懂。

  16. lola
    *.*.*.*
    链接

    lola 2009-08-07 09:23:00

    这样的写法,真是难以理解,其他语言的用户会觉得挺怪异

  17. FredChen
    *.*.*.*
    链接

    FredChen 2009-08-07 09:30:00

    使用示例一 返回数字列表的代码好像写错了吧.
    List<int> squareList = new List<int>();
    foreach (int i in positiveList) evenList.Add(i * i);

    evenList.Sort();
    return squareList;

    positiveList不知从哪里冒出来的.
    返回的squareList是空的.呵呵~

  18. 妖居
    *.*.*.*
    链接

    妖居 2009-08-07 09:31:00

    老赵关于你的例子一,
    return strList
    .Select(s => Int32.Parse(s)) // 转成整数
    .Where(i => i % 2 == 0) // 找出所有大于零的数
    .Select(i => i * i) // 算出每个数的平方
    .OrderBy(i => i) // 按照元素自身排序
    .ToList(); // 构造一个List
    我在实际工作中发现,如果其中一个方法调用除了Bug很难排查。因为这条语句会一下子都执行了,不知道到底是Select写错了,还是OrderBy写错了,或者ToList除了问题。所以我只能把这些语句拆成一个一个的Debug,通过之后再连起来。

    不知道你有没有遇到类似的问题,如何解决。

    难道是我编程水平太低了 - -!!!

  19. 老赵
    admin
    链接

    老赵 2009-08-07 09:33:00

    @lola
    说了半天你还是觉得“难以理解”啊?我觉得非常清晰。那个GroupBy...ToDictionary的设计多么经典。
    当然你说的如果是其他语言用户……Scheme里的beautiful code更难理解,这是正常的。
    但我认为,其他语言用户都不了解C#,谈啥清晰。而且我们其实也不需要其他语言用户看得懂,除非他学过C#,呵呵。

  20. 麦舒
    *.*.*.*
    链接

    麦舒 2009-08-07 09:34:00

    Lambda 有个很大的缺陷,就是无法调试。曾经试过为了调试, 把 Lambda 转为 delegate 。

  21. trunks
    *.*.*.*
    链接

    trunks 2009-08-07 09:35:00

    老赵的文章总是有的一读,虽然这些概念在俺脑子里都有,但是却无法总结归纳抽象出来,读老赵的文章总有理顺的感觉。

  22. 老赵
    admin
    链接

    老赵 2009-08-07 09:36:00

    @FredChen
    多谢提醒,已经改正了。
    // 不过这也从侧面说明了这样写代码容易出错,呵呵。

  23. 老赵
    admin
    链接

    老赵 2009-08-07 09:38:00

    @妖居
    这句话为什么需要调试?是不是其中写的太复杂了?例如Select里面一大堆……
    如果太复杂的话,首先设法降低复杂度,例如把Select里面的复杂东西单独提取出来。
    或者,暂时用delegate代替,方便设断点。

  24. 老赵
    admin
    链接

    老赵 2009-08-07 09:39:00

    麦舒:Lambda 有个很大的缺陷,就是无法调试。曾经试过为了调试, 把 Lambda 转为 delegate 。


    我基本上没有遇到过需要调试Lambda的情况,因为Lambda表达式里一般总是很简单,如果需要复杂的话,就直接写成多行的Lambda了,这样对调试没有障碍。
    Lambda的问题在于——在调试时,Watch和Immidiate Window里没有办法使用Lambda表达式,有些不爽,呵呵。

  25. 老赵
    admin
    链接

    老赵 2009-08-07 09:43:00

    trunks:老赵的文章总是有的一读,虽然这些概念在俺脑子里都有,但是却无法总结归纳抽象出来,读老赵的文章总有理顺的感觉。


    所以写文章对于锻炼表达能力,理清思路是很有帮助的。

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

    韦恩卑鄙 2009-08-07 09:44:00

    吐槽来了
    上一篇忽悠我们只有(下) 现在竟然有(中)了 没诚信阿~~~

  27. wq.dotnet
    *.*.*.*
    链接

    wq.dotnet 2009-08-07 09:45:00

    很爽... 有一种写 JQuery 的感觉

  28. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-07 09:47:00

    Jeffrey Zhao:

    麦舒:Lambda 有个很大的缺陷,就是无法调试。曾经试过为了调试, 把 Lambda 转为 delegate 。


    我基本上没有遇到过需要调试Lambda的情况,因为Lambda表达式里一般总是很简单,如果需要复杂的话,就直接写成多行的Lambda了,这样对调试没有障碍。
    Lambda的问题在于——在调试时,Watch和Immidiate Window里没有办法使用Lambda表达式,有些不爽,呵呵。


    呵呵,是intermediate还是Immidiate ?

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

    韦恩卑鄙 2009-08-07 09:49:00

    .Where(i => i % 2 == 0) // 找出所有大于零的数

    貌似注释写错鸟

    嘿嘿

  30. 入门级[未注册用户]
    *.*.*.*
    链接

    入门级[未注册用户] 2009-08-07 09:50:00



    哇塞,老赵思路好清晰哦


    怎一个“爽”字了得

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

    韦恩卑鄙 2009-08-07 09:52:00

    vb的 function 还没办法多行呢

    泪流满面

  32. *僵@尸.net国@宝*
    *.*.*.*
    链接

    *僵@尸.net国@宝* 2009-08-07 09:53:00

    顶顶。

  33. iceboundrock
    *.*.*.*
    链接

    iceboundrock 2009-08-07 09:55:00

    @trunks
    确实是,理解一个东西是一个层次,能流畅的把这个东西表达出来又是另外一个层次。

  34. Andy Ge[未注册用户]
    *.*.*.*
    链接

    Andy Ge[未注册用户] 2009-08-07 09:55:00

    老赵这篇博文写的相当到位,两个例子举的也是贴切不过了,赞一个...

  35. -brian-
    *.*.*.*
    链接

    -brian- 2009-08-07 09:58:00

    很不错,觉得这种写法很清晰

  36. 老赵
    admin
    链接

    老赵 2009-08-07 09:59:00

    DiggingDeeply:
    呵呵,是intermediate还是Immidiate ?


    Immidiate Windows,就是“立即窗口”,一个小命令行样的东西,边敲代码边执行那种咯。

  37. 老赵
    admin
    链接

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

    韦恩卑鄙:
    .Where(i => i % 2 == 0) // 找出所有大于零的数
    貌似注释写错鸟
    嘿嘿


    第一个示例改过一次,看来直接改HTML代码就是容易疏忽,嗯嗯。

  38. 南院那
    *.*.*.*
    链接

    南院那 2009-08-07 10:00:00

    看了好多篇了,终于让老赵给点化了。
    继续学习。

  39. Anders Cui
    *.*.*.*
    链接

    Anders Cui 2009-08-07 10:03:00

    果然在语义上是很有帮助的。从老赵的例子可以看出,拉面表达式的主要作用是将简短的代码内联化,不用在其它地方进行额外定义,从而可以一气呵成。

  40. Anders Cui
    *.*.*.*
    链接

    Anders Cui 2009-08-07 10:04:00

    韦恩卑鄙:
    vb的 function 还没办法多行呢

    泪流满面


    时下应该是内牛满面:)

  41. Ray Z
    *.*.*.*
    链接

    Ray Z 2009-08-07 10:05:00

    写得真是好!

  42. 玉开
    *.*.*.*
    链接

    玉开 2009-08-07 10:06:00

    赞老赵同学。

  43. 稳扎稳打
    *.*.*.*
    链接

    稳扎稳打 2009-08-07 10:11:00

    纯粹玩技术~~~牛啊~

  44. 日本麒麟[未注册用户]
    *.*.*.*
    链接

    日本麒麟[未注册用户] 2009-08-07 10:12:00

    博主射了

  45. yzlhccdec
    *.*.*.*
    链接

    yzlhccdec 2009-08-07 10:16:00

    我一直很爱lambda,虽然会稍微慢些

  46. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-07 10:19:00

    @Jeffrey Zhao
    百度和金山都查不出来啊。
    http://dict.baidu.com/s?wd=Immidiate&tn=dict

    你的文章思想性很好,比我强太多了。
    Func系列委托最多就4个参数和1个结果,多余4个参数怎么解决呢?用集合类或是数组是不是在表达式中展开会造成语法复杂化?你有没有遇到类似的问题?

    还有就是蓝不大表达式之所以不能调试,我认为是由csc生成的附加代码来包装表达式,而在蓝不大表达式代码运行的时候,则构造出一个Func委托对象来挂上前面生成的附加代码,所有的这些都是在C#级别不可见的,所以根本没有办法加断点。
    有一个办法可以调试,用#develop来调试IL。

  47. 老赵
    admin
    链接

    老赵 2009-08-07 10:21:00

    @DiggingDeeply
    是Immediate Window……拼错了……
    多于四个参数就自己创建更多Func和Action委托咯。
    // .NET 4.0已经帮我们内置了最多达16个参数的Action和Func……

  48. 老赵
    admin
    链接

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

    DiggingDeeply:
    还有就是蓝不大表达式之所以不能调试,我认为是由csc生成的附加代码来包装表达式,而在蓝不大表达式代码运行的时候,则构造出一个Func委托对象来挂上前面生成的附加代码,所有的这些都是在C#级别不可见的,所以根本没有办法加断点。
    有一个办法可以调试,用#develop来调试IL。


    我觉得你的说法有一定道理,事实上似乎是写成一行就不行了,例如,这两种都没有办法调试:
    OrderBy(i => i)
    OrderBy(delegate(int i) { return i; })。

    但是下面这种是可以调试的。
    OrderBy(i =>
    {
      return i;
    })

  49. .easycode[未注册用户]
    *.*.*.*
    链接

    .easycode[未注册用户] 2009-08-07 10:26:00

    老赵文章 我都坚持看完 总算没被时代所抛弃
    很好 很强大

  50. 麒麟.NET
    *.*.*.*
    链接

    麒麟.NET 2009-08-07 10:27:00

    @DiggingDeeply
    其实Func就是一个有返回值的委托定义,如果参数过多,完全可以自己定义嘛

  51. 老赵
    admin
    链接

    老赵 2009-08-07 10:28:00

    yzlhccdec:我一直很爱lambda,虽然会稍微慢些


    性能问题咱下次再谈,呵呵。

  52. cnhzlt
    *.*.*.*
    链接

    cnhzlt 2009-08-07 10:43:00

    写的太好了

  53. lovecherry_111[未注册用户…
    *.*.*.*
    链接

    lovecherry_111[未注册用户] 2009-08-07 10:47:00

    from a in b select new c
    {
    aa = a.aa
    bb = from c in d select c.ee
    }
    这种嵌套一多的话很容易出错,写起来爽,但调试是个问题啊

  54. 装配脑袋
    *.*.*.*
    链接

    装配脑袋 2009-08-07 10:54:00

    C#的Lambda函数不能in place调用,所以很多东东写不出来。
    例如
    (Function(eap) If(Not String.IsNullOrEmpty(eap), <ExternalAcessPrefix><%= eap %></ExternalAcessPrefix>, Nothing))(profile.<rh:ExternalAccessPrefix>.Value)

  55. 誉彬
    *.*.*.*
    链接

    誉彬 2009-08-07 11:02:00

    支持老赵,不要因为那个日本猪影响了自己的心情

  56. 过客5178[未注册用户]
    *.*.*.*
    链接

    过客5178[未注册用户] 2009-08-07 11:08:00

    请问老赵,如何单步调试这种程序呀?

  57. Vadin Wang
    *.*.*.*
    链接

    Vadin Wang 2009-08-07 11:10:00

    老赵总是那么时髦,那么拉风..

  58. asp@net[未注册用户]
    *.*.*.*
    链接

    asp@net[未注册用户] 2009-08-07 11:18:00

    妖居:
    老赵关于你的例子一,
    return strList
    .Select(s => Int32.Parse(s)) // 转成整数
    .Where(i => i % 2 == 0) // 找出所有大于零的数
    .Select(i => i * i) // 算出每个数的平方
    .OrderBy(i => i) // 按照元素自身排序
    .ToList(); // 构造一个List
    我在实际工作中发现,如果其中一个方法调用除了Bug很难排查。因为这条语句会一下子都执行了,不知道到底是Select写错了,还是OrderBy写错了,或者ToList除了问题。所以我只能把这些语句拆成一个一个的Debug,通过之后再连起来。

    不知道你有没有遇到类似的问题,如何解决。

    难道是我编程水平太低了 - -!!!




    ----------------------
    请问一下:
    .Select(s => Int32.Parse(s))
    s => Int32.Parse(s) 这里怎么做异常处理?

  59. lhking
    *.*.*.*
    链接

    lhking 2009-08-07 11:19:00

    老赵这一写,就三篇。

  60. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-08-07 11:20:00

    declarative 的比 imperative 的难调试这是个通病,就好象 xaml 也是这样,Silverlight 里 xaml 出错了很难找到具体原因的,很多时候只能靠肉眼观察。

    在方便编写/阅读和方便调试之间,也有一个平衡吧,这就要靠个人的经验去掌握了。

  61. 、clōυd'
    *.*.*.*
    链接

    、clōυd' 2009-08-07 11:21:00

    确实很好,虽然我学的不够扎实但是,我也基本理解了。现在我们公司做项目是用ASP.NET MVC 开发,我之前都不怎么接触过。一片迷茫啊。

  62. 老赵
    admin
    链接

    老赵 2009-08-07 11:23:00

    asp@net:
    请问一下:
    .Select(s => Int32.Parse(s))
    s => Int32.Parse(s) 这里怎么做异常处理?


    假设这里不需要做异常处理,如果需要做异常处理,就不用这种方式做了。
    例如,你可以根据你的异常处理策略先把strList转为IntList,然后对IntList开始Where...Select...OrderBy等等。

  63. 老赵
    admin
    链接

    老赵 2009-08-07 11:25:00

    、clōυd':确实很好,虽然我学的不够扎实但是,我也基本理解了。现在我们公司做项目是用ASP.NET MVC 开发,我之前都不怎么接触过。一片迷茫啊。


    作ASP.NET MVC开发的,基本上一定要了解这些。
    微软API目前的趋势是越来越Fluent,其中大量依赖Lambda表达式构造Expression Tree或匿名方法。

  64. NeweggMall
    *.*.*.*
    链接

    NeweggMall 2009-08-07 11:25:00

    深入浅出,讲的很清晰,支持!

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

    yeml[未注册用户] 2009-08-07 11:28:00

    那么您可能就会问,这样看来Lambda表达式又有什么意义呢?Lambda表达式的意义便是它可以写的非常简单,例如之前的Lambda表达式可以简写成这样:

    Func<int, int, int> max = (a, b) =>
    {
    if (a > b)
    {
    return a;
    }
    else
    {
    return b;
    }
    };

    ----------------------------------------
    这个跟“ Lambda”有什么关系呢?我认为这个只是。net 3.5提供的变量类型自动根据上下文推断而已,就好像var i=0,一个道理
    还是 delegate(){};来的实在。

  66. 、clōυd'
    *.*.*.*
    链接

    、clōυd' 2009-08-07 11:33:00

    @Jeffrey Zhao
    恩,以后多关注你的文章咯,多跟你学习学习,我还是初学者呢,又是个女生。发现,自己好像不适合开发。

  67. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-07 11:35:00

    λ表达式能不能调试是不是关键在你的表达式里在有没有呢?
    我猜的,结论是断点只能断在语句上,不能断在表达式上。

  68. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-07 11:38:00

    、clōυd':
    @Jeffrey Zhao
    恩,以后多关注你的文章咯,多跟你学习学习,我还是初学者呢,又是个女生。发现,自己好像不适合开发。


    女生做开发,我见过,各个劲头十足,但是效果。。。

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

    Tooth[未注册用户] 2009-08-07 11:42:00

    我也觉得lambda只不过是func几个函数的变形罢了,但是编辑器会自动的把=>这种形式向func那几个委托上去匹配,所以仅是语法表象变了,实质还是最多和2.0一样。
    举的那个例子不仅是lambda,扩展方法,还有延迟执行的思想在里面。
    我觉得变化上最大的应该是expression tree 吧,它好像可以在编译时发现某些要执行的语句是否错误,以我通过反射或者ioc在运行时才能发现的错误就不存在了,提高了开发质量。具体细节我还不太说的明白,好读书不求甚解。
    最后,我们这个系列不是讨论招聘问题的吗,有点演化为技术大讨论了,偏题。

  70. 菜鸟毛
    *.*.*.*
    链接

    菜鸟毛 2009-08-07 11:42:00

    老赵,上面好像有一个地方错误哦,不知道是不是我错了.
    static List<int> GetSquaresOfPositive(List<string> strList)
    {
    List<int> intList = new List<int>();
    foreach (var s in strList) intList.Add(Int32.Parse(s));

    List<int> evenList = new List<int>();
    foreach (int i in intList)
    {
    if (i % 2 == 0) evenList.Add(i);
    }

    List<int> squareList = new List<int>();
    foreach (int i in positiveList) squareList.Add(i * i);

    squareList.Sort();
    return squareList;
    }

    倒数第三句的positiveList应该为evenList吗?
    请指教!

  71. 、clōυd'
    *.*.*.*
    链接

    、clōυd' 2009-08-07 11:43:00

    DiggingDeeply:

    、clōυd':
    @Jeffrey Zhao
    恩,以后多关注你的文章咯,多跟你学习学习,我还是初学者呢,又是个女生。发现,自己好像不适合开发。


    女生做开发,我见过,各个劲头十足,但是效果。。。



    唉,受打击了。所以,目前有想法转做软件售后或者去坐测试吧。如果你们有好的建议不妨提提啊、。谢谢!

  72. 菜鸟毛
    *.*.*.*
    链接

    菜鸟毛 2009-08-07 11:45:00

    吾认为,做开发与性别无关,与态度有很大关系.首先浮躁是影响提升的一个重要阻碍.

  73. 老赵
    admin
    链接

    老赵 2009-08-07 11:47:00

    @菜鸟毛
    莫客气,很显然,你说的没错,呵呵。

  74. 老赵
    admin
    链接

    老赵 2009-08-07 11:49:00

    、clōυd':
    目前有想法转做软件售后或者去坐测试吧。如果你们有好的建议不妨提提啊、。谢谢!


    测试不比开发容易,测试也要懂很多技术。

  75. 老赵
    admin
    链接

    老赵 2009-08-07 11:50:00

    Tooth:
    最后,我们这个系列不是讨论招聘问题的吗,有点演化为技术大讨论了,偏题。


    谁说这个系列是讨论招聘问题的,从一开始就是在谈论技术,就像这标题明摆着……

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

    韦恩卑鄙 2009-08-07 11:58:00

    楼上的真能忽悠 以上

  77. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-07 11:59:00

    @、clōυd'
    给你个建议:女人干的好,不如嫁的好。

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

    PXDIO[未注册用户] 2009-08-07 12:01:00

    C语言也没有那么多的语言特性, 为什么C语言却比C#强大许多倍呢??
    这是个问题呀...
    C#会不会成为玩具语言呀,,呵呵...

  79. 老赵
    admin
    链接

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

    yeml:
    这个跟“ Lambda”有什么关系呢?我认为这个只是。net 3.5提供的变量类型自动根据上下文推断而已,就好像var i=0,一个道理
    还是 delegate(){};来的实在。


    因为有=>这种形式,所以叫“Lambda”,这就是Lambda的定义啊。
    而且,这只是“演变”过程中的一部分,后面不是还有继续吗?不要看到中段就下结论啊,后面i => i这种就不是delegate能代替的了的。

    而且最后,我也说了,以下两种方法随意,没有优劣之分:
    delegate(int a, bool b) { ... }
    (int a, bool b) => { ... }

    但是如果是这两者区别就大了:
    delegate(Dictionary<int, string> a, IGrouping<string, Func<string, bool, int>> b) { ... }
    (a, b) => { ... }

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

    韦恩卑鄙 2009-08-07 12:02:00

    Jeffrey Zhao:

    、clōυd':
    目前有想法转做软件售后或者去坐测试吧。如果你们有好的建议不妨提提啊、。谢谢!


    测试不比开发容易,测试也要懂很多技术。


    我们这边的测试 每天写脚本 眼泪哗哗的

  81. 老赵
    admin
    链接

    老赵 2009-08-07 12:04:00

    PXDIO:
    C语言也没有那么多的语言特性, 为什么C语言却比C#强大许多倍呢??
    这是个问题呀...
    C#会不会成为玩具语言呀,,呵呵...


    谁说C语言比C#强大许多倍呢?只是面向功能不同而已。
    谁说写操作系统,就比写Web应用强大?分工不同,无法互相替代。
    C#变强,.NET平台也在增强,都是实打实的,因此我打赌C#肯定不会是玩具语言。

    补充一句,前天我写了一个chm转prc电子书格式的小程序(可以放在kindle上看),用C#一个晚上就搞定了。
    真是无数快感。如果一个语言等让我很爽地完成各种工作,玩具就玩具了,谁说玩具不是工具,嘿嘿。

  82. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-07 12:04:00

    韦恩卑鄙:

    Jeffrey Zhao:

    、clōυd':
    目前有想法转做软件售后或者去坐测试吧。如果你们有好的建议不妨提提啊、。谢谢!


    测试不比开发容易,测试也要懂很多技术。


    我们这边的测试 每天写脚本 眼泪哗哗的


    也内牛满面了。

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

    韦恩卑鄙 2009-08-07 12:09:00

    一般说c语言比 c#强大的 c语言也就写到hello world 而已

  84. iceboundrock
    *.*.*.*
    链接

    iceboundrock 2009-08-07 12:34:00

    PXDIO:
    C语言也没有那么多的语言特性, 为什么C语言却比C#强大许多倍呢??
    这是个问题呀...
    C#会不会成为玩具语言呀,,呵呵...


    不知道”强大很多倍“是怎么算出来的
    玩具语言微软倒是出了一个,不过不是C#,而是Small Basic。确实很好玩的,http://msdn.microsoft.com/en-us/devlabs/cc950524.aspx

  85. 、clōυd'
    *.*.*.*
    链接

    、clōυd' 2009-08-07 13:12:00

    @DiggingDeeply
    可是,这也不是说好就可以的啊。

  86. YokerWu[未注册用户]
    *.*.*.*
    链接

    YokerWu[未注册用户] 2009-08-07 13:31:00

    哈哈~~

    故意把代码写错几个地方的吧。

  87. webaspx
    *.*.*.*
    链接

    webaspx 2009-08-07 13:43:00

    相当不错,学习了,支持下!!关于Lambda我还要继续学习啊。

  88. Coolin
    *.*.*.*
    链接

    Coolin 2009-08-07 13:52:00

    相当不错,本人相当喜欢老赵的文章

  89. 妖居
    *.*.*.*
    链接

    妖居 2009-08-07 13:57:00

    @Jeffrey Zhao
    是,不是Select一大堆,是Where一大堆,里面不仅有Func,还有Expression<Func>,所有搞的时候会出现错误。

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

    韦恩卑鄙 2009-08-07 13:58:00

    small basic 真的让我想起幼儿园第一次看到basic的情景阿



    话说以前的 gbasic 也是玩具语言 -0-

  91. 老赵
    admin
    链接

    老赵 2009-08-07 14:03:00

    @妖居
    太复杂就拆分,不过我真的很少见到Func和Expression<Func>一起用的这种,至少一时想不起来。

  92. 妖居
    *.*.*.*
    链接

    妖居 2009-08-07 14:07:00

    Jeffrey Zhao:
    @妖居
    太复杂就拆分,不过我真的很少见到Func和Expression<Func>一起用的这种,至少一时想不起来。



    一个比较复杂的查询,通过Entity Framework,可能需要关联很多的表,查询条件也不是简单是匹配,可能条件里面也是一个列表,匹配了其中任何一个就可以,所以搞得Where很复杂。最终像你说的拆开来写,便于调试,可比较容易看清楚。

    另外你说的lambda表达式不能Watch深有同感啊……希望2010能够支持。

  93. 老赵在这么多人的追捧称赞中,满足了,高潮了,射了。[未注册用户…
    *.*.*.*
    链接

    老赵在这么多人的追捧称赞中,满足了,高潮了,射了。[未注册用户] 2009-08-07 14:08:00

    老赵在这么多人的追捧称赞中,满足了,高潮了,射了。

  94. DiggingDeeply
    *.*.*.*
    链接

    DiggingDeeply 2009-08-07 14:13:00

    妖居:

    Jeffrey Zhao:
    @妖居
    太复杂就拆分,不过我真的很少见到Func和Expression<Func>一起用的这种,至少一时想不起来。



    一个比较复杂的查询,通过Entity Framework,可能需要关联很多的表,查询条件也不是简单是匹配,可能条件里面也是一个列表,匹配了其中任何一个就可以,所以搞得Where很复杂。最终像你说的拆开来写,便于调试,可比较容易看清楚。

    另外你说的lambda表达式不能Watch深有同感啊……希望2010能够支持。


    表很多的话还要用LINQ吗?这么多join写在代码里,看都麻烦,更别说调试了?

  95. 殷敏峰
    *.*.*.*
    链接

    殷敏峰 2009-08-07 18:47:00

    个人觉得Jeffrey Zhao相当负责。

  96. 戴伟
    *.*.*.*
    链接

    戴伟 2009-08-07 20:11:00

    小日本!懂个屁!滚!做过对日的都知道那些东西什么玩意儿~!顶楼主!

  97. njin
    *.*.*.*
    链接

    njin 2009-08-07 23:24:00

    又扩展了,老赵真能讲啊 ^_^

    map filter都有了,那么reduce有吗?

  98. 老赵
    admin
    链接

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

    @njin
    reduce就是把n个值变一个,其实感觉和Aggregate/fold差不多。

  99. Taven
    *.*.*.*
    链接

    Taven 2009-08-08 08:45:00

    注3:除非我们补充Func、Enumerable,Dictionary,Grouping等类型及API,否则这段代码在Java中是无法编译通过的。事实上,这段Java代码是我在记事本中写出来的。不过这个形式完全正确。

    ---------------

    牛X

  100. 小青青
    *.*.*.*
    链接

    小青青 2009-08-08 08:46:00

    老赵,你说的很对,对java这种语言真的有种恨铁不成钢的感觉,
    java没有委托也就成了它语言发展的瓶颈(貌似连in,out都要通过反射机制才能实现)。
    MS推出了lambda、linq,使C#语言越来越成熟、强大,编写程序真的越来越充满乐趣了。

  101. 鹤冲天
    *.*.*.*
    链接

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

    以前不会用Lambda表达式,不知道做什么的。
    现在会了,发现不用Lambda表达式编不太程序了。

  102. 老赵
    admin
    链接

    老赵 2009-08-08 22:42:00

    @小青青
    in/out通过反射机制实现?这点我倒不知道。
    in/out是直接操作指针的值,其实就是传递了指针的指针,不知道和反射有什么关系。

  103. 菌哥
    *.*.*.*
    链接

    菌哥 2009-08-08 22:46:00

    老赵的文章每一篇我都认真看!
    学到不少东西

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

    yeml[未注册用户] 2009-08-09 01:13:00

    菌哥:
    老赵的文章每一篇我都认真看!
    学到不少东西


    这句话实在,我也从zhao这里学到不少东西,谢谢
    某些人不要把我当成马屁精啦,我毕业6年了,自己做了一个14万多行的软件,不是那些刚毕业的小孩,是确实从zhao这里学到了东西,不主要是某一项或几项技术,而是一种治学的严谨的风格。

  105. 老赵
    admin
    链接

    老赵 2009-08-09 21:59:00

    @yeml
    你的邮箱是多少啊,我有NHibernate的问题找不到你……

  106. AlexLiu
    *.*.*.*
    链接

    AlexLiu 2009-08-09 22:10:00

    真是不错啊。。。
    认认真真的看了2个,写了几百行。对delegate有了新的认识。
    期待下集。
    不过你说了Func,应该把Action也提一句。这就全了。

  107. 老赵
    admin
    链接

    老赵 2009-08-10 09:33:00

    @AlexLiu
    我倒觉得没必要,因为我说的是委托,不是特定类型的委托。
    用Func也不是为了介绍Func,只是刚好用到了而已。

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

    蜗牛身上的一只蚂蚁 2009-08-10 13:58:00

    赵老师。那个注1、2、3之类的上标,怎么弄上去的。。,如果有空闲时间能回最好。。谢谢

  109. 老赵
    admin
    链接

    老赵 2009-08-10 14:01:00

    @蜗牛身上的一只蚂蚁
    就是普通的HTML标签,自己看一下吧。

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

    蜗牛身上的一只蚂蚁 2009-08-10 14:02:00

    谢谢。。。还真没有往HTML标签上去想。。呵呵

  111. flyingchen
    *.*.*.*
    链接

    flyingchen 2009-08-10 23:31:00

    @妖居
    同感;
    另外在大块的构造函数+初始化功能代码中也比较难调试

  112. jivi
    *.*.*.*
    链接

    jivi 2009-08-11 16:47:00

    受益非浅

  113. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-08-12 02:25:00

    嗯……C# 3.0的lambda表达式有许多有趣的地方,甚至允许不少“滥用”方法。像是我毕业论文里的一段示例代码:

    public partial class CalculatorGrammar : GrammarBase<int> {
      public CalculatorGrammar( ) {
        var expr = NonTerminal( "expr" );
        var term = NonTerminal( "term" );
        var factor = NonTerminal( "factor" );
        var Num = IntegerTerminal( "Num" );
        
        Rules(
          // expr -> term ("+" term)*
          exprRule => term + ( "+" + term ).Star( )
          ,
          // term -> factor ("*" factor)*
          termRule => factor + ( "*" + factor ).Star( )
          ,
          // factor -> "(" expr ")"
          //         | Num
          factorRule => "(" + expr + ")"
                      | Num
        );
        
        StartSymbol = expr;
      }
    }
    

    这个用Java或者老C#就难表达了……我滥用了lambda表达式来表示EBNF记法中的箭头,呵呵~

  114. 老赵
    admin
    链接

    老赵 2009-08-12 09:04:00

    @RednaxelaFX
    想到用C#表示EBNF,很有一套啊!

  115. RednaxelaFX
    *.*.*.*
    链接

    RednaxelaFX 2009-08-12 09:16:00

    @Jeffrey Zhao
    嗯,我毕设做的是一个parser generator,在运行时根据用C#表示的EBNF来直接生成可执行的解析器。我觉得这可以称为“runnable specification”的一种吧,没有lambda和类型推导、运算符重载、Expression Tree的话,我就做不到这种视觉效果了。内部DSL的意义不就是“视觉效果”么
    呵呵,想了很久都没想出我觉得完美的表示方式,但最后就快要答辩了,没办法,只好做了些妥协。像是声明符号的时候非要让变量名和传进工厂方法的字符串参数一致(为了分析Expression Tree时方便)、指定规则时箭头左边要加Rule后缀(因为C#不允许嵌套作用域内有重名的变量)之类的,都让我觉得很不爽……有机会慢慢修一下或许会放出来吧 ^ ^

  116. 老赵
    admin
    链接

    老赵 2009-08-12 09:47:00

    @RednaxelaFX
    就是这个意思,呵呵。

  117. xland
    *.*.*.*
    链接

    xland 2009-08-12 18:14:00

    很简单,不是吗?相信您一定可以一蹴而就:

    一蹴而就

  118. xland
    *.*.*.*
    链接

    xland 2009-08-12 18:40:00

    wq.dotnet:很爽... 有一种写 JQuery 的感觉


  119. @虫子
    *.*.*.*
    链接

    @虫子 2009-08-18 22:47:00

    看得太过瘾了,又被学习了。

  120. 链接

    张占岭 2014-11-01 22:48:16

    HI,老赵,你好,我目前正在做一个缓存系统,而这个缓存是关于键值对存储的,而其中键我希望使用“方法签名”来实现,而对于方法的参数为lambda表达式时,出现了一点问题,现在我希望得到一个lambda表达式的字符串的形式,您能帮我想想吗,或者lambda表达式的唯一标识也行。

发表回复

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

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

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

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

使用Live Messenger联系我