一区二区久久-一区二区三区www-一区二区三区久久-一区二区三区久久精品-麻豆国产一区二区在线观看-麻豆国产视频

從.NET中委托寫法的演變談開去(中):Lambda表達式及其優勢

上一篇文章中我們簡單探討了.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)”要統一、省事一些吧。

相關文章

  • 從.NET中委托寫法的演變談開去(上):委托與匿名方法
  • 從.NET中委托寫法的演變談開去(中):Lambda表達式及其優勢
  • 從.NET中委托寫法的演變談開去(下):性能相關

注1:嚴格說來,這里的body是一個“語句(statement)”,而不是“表達式(expression)”。因為一個委托其實是一個方法,因此使用Lambda來表示一個委托,其中必然要包含“語句”。不過在目前的C#中,Lambda表達式還有一個作用是構造一顆“表達式樹”,而目前的C#編譯器只能構造“表達式樹”而不是“語句樹”。

注2:事實上,在.NET 2.0使用delegate關鍵字定義匿名方法時已經可以有些許“類型推演”的意味了——雖然還是必須寫明參數的類型,但是我們已經可以省略委托的類型了,不是嗎?

注3:除非我們補充Func、Enumerable,Dictionary,Grouping等類型及API,否則這段代碼在Java中是無法編譯通過的。事實上,這段Java代碼是我在記事本中寫出來的。不過這個形式完全正確。

NET技術從.NET中委托寫法的演變談開去(中):Lambda表達式及其優勢,轉載需保留來源!

鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播更多信息之目的,如作者信息標記有誤,請第一時間聯系我們修改或刪除,多謝。

主站蜘蛛池模板: 四虎永久在线观看视频精品 | 天天做天天爱天天爽综合区 | 露脸真实国产精品自在 | 欧美一区二区三区视频 | 一本久道久久综合多人 | 色视频www在线播放国产人成 | 玖玖玖视频在线观看视频6 玖玖免费 | 国产欧美激情一区二区三区 | 欧美黄色片在线观看 | 色小妹综合 | 涩涩涩涩涩涩涩涩涩涩 | 91精品免费国产高清在线 | 视频一区二区在线播放 | 99在线观看视频免费 | 成人午夜视频免费 | 国产在线观看一区 | 欧美特黄aaaaa | 美女视频黄频大全免费 | 亚州视频一区二区 | 欧美一区二区三区视视频 | 亚洲94vvv男人的天堂五月 | 国产亚洲精品国产第一 | 国产精品极品美女免费观看 | 成人在线激情 | 免费大黄网站在线观看 | 亚洲国产精品67194成人 | 国产亚洲精品sese在线播放 | 91免费看国产 | 国产视频二 | 成年人视频免费在线播放 | 91小视频版在线观看www | 免费看黄色录像 | 亚洲午夜视频在线观看 | 四虎最新网 | 激情视频图片小说 | 综合色播| 欧美一区二区自偷自拍视频 | 国产玖玖 | 在线观看成人免费 | 一级风流片a级国产 | 中文字幕无线码中文字幕免费 |