.NET中那些所謂的新語法之二:匿名類、匿名方法與擴充套件方法
開篇:在上一篇中,我們瞭解了自動屬性、隱式型別、自動初始化器等所謂的新語法,這一篇我們繼續征程,看看匿名類、匿名方法以及常用的擴充套件方法。雖然,都是很常見的東西,但是未必我們都明白其中蘊含的奧妙。所以,跟著本篇的步伐,繼續來圍觀。
/* 新語法索引 */
一、匿名類:[ C# 3.0/.NET 3.x 新增特性 ]
1.1 不好意思,我匿了
在開發中,我們有時會像下面的程式碼一樣宣告一個匿名類:可以看出,在匿名類的語法中並沒有為其命名,而是直接的一個new { }就完事了。從外部看來,我們根本無法知道這個類是幹神馬的,也不知道它有何作用。
var annoyCla1 = new{ ID = 10010, Name = "EdisonChou", Age = 25 }; Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla1.ID,annoyCla1.Name, annoyCla1.Age);
經過除錯執行,我們發現匿名類完全可以實現具名類的效果:
1.2 深入匿名類背後
既然我們發現匿名類可以完全實現具名類的效果,那麼我們可以大膽猜測編譯器肯定在內部幫我們生成了一個類似具名類的class,於是,我們還是藉助反編譯工具對其進行探索。通過Reflector
從上圖可以看出:
(1)匿名類被編譯後會生成一個[泛型類],可以看到上圖中的<>f__AnonymousType0<<ID>j__TPar, <Name>j__TPar, <Age>j__TPar>就是一個泛型類;
(2)匿名類所生成的屬性都是只讀的,可以看出與其對應的欄位也是只讀的;
所以,如果我們在程式中為屬性賦值,那麼會出現錯誤;
(3)可以看出,匿名類還重寫了基類的三個方法:Equals,GetHashCode和ToString;我們可以看看它為我們所生成的ToString方法是怎麼來實現的:
實現的效果如下圖所示:
1.3 匿名類的共享
可以想象一下,如果我們的程式碼中定義了很多匿名類,那麼是不是編譯器會為每一個匿名類都生成一個泛型類呢?答案是否定的,編譯器考慮得很遠,避免了重複地生成型別。換句話說,定義了多個匿名類的話如果符合一定條件則可以共享一個泛型類。下面,我們就來看看有哪幾種情況:
(1)如果定義的匿名類與之前定義過的一模一樣:屬性型別和順序都一致,那麼預設共享前一個泛型類
var annoyCla1 = new { ID = 10010, Name = "EdisonChou", Age = 25 }; Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla1.ID, annoyCla1.Name, annoyCla1.Age); Console.WriteLine(annoyCla1.ToString()); // 02.屬性型別和順序與annoyCla1一致,那麼共同使用一個匿名類 var annoyCla2 = new { ID = 10086, Name = "WncudChou", Age = 25 }; Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla1.ID, annoyCla1.Name, annoyCla1.Age); Console.WriteLine("Is The Same Class of 1 and 2:{0}", annoyCla1.GetType() == annoyCla2.GetType());
通過上述程式碼中的最後兩行:我們可以判斷其是否是一個型別?答案是:True
(2)如果屬性名稱和順序一致,但屬性型別不同,那麼還是共同使用一個泛型類,只是泛型引數改變了而已,所以在執行時會生成不同的類:
var annoyCla3 = new { ID = "EdisonChou", Name = 10010, Age = 25 }; Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla3.ID, annoyCla3.Name, annoyCla3.Age); Console.WriteLine("Is The Same Class of 2 and 3:{0}", annoyCla3.GetType() == annoyCla2.GetType());
我們剛剛說到雖然共享了同一個泛型類,只是泛型引數改變了而已,所以在執行時會生成不同的類。所以,那麼可以猜測到最後兩行程式碼所顯示的結果應該是False,他們雖然都使用了一個泛型類,但是在執行時生成了兩個不同的類。
(3)如果資料型名稱和型別相同,但順序不同,那麼編譯器會重新建立一個匿名類
var annoyCla4 = new { Name = "EdisonChou", ID = 10010, Age = 25 }; Console.WriteLine("ID:{0}-Name:{1}-Age:{2}", annoyCla4.ID, annoyCla4.Name, annoyCla4.Age); Console.WriteLine("Is The Same Class of 2 and 4:{0}", annoyCla4.GetType() == annoyCla2.GetType());
執行判斷結果為:False
通過Reflector,可以發現,編譯器確實重新生成了一個泛型類:
二、匿名方法:[ C# 2.0/.NET 2.0 新增特性 ]
2.1 從委託的宣告說起
C#中的匿名方法是在C#2.0引入的,它終結了C#2.0之前版本宣告委託的唯一方法是使用命名方法的時代。不過,這裡我們還是看一下在沒有匿名方法之前,我們是如何宣告委託的。
(1)首先定義一個委託型別:
public delegate void DelegateTest(string testName);
(2)編寫一個符合委託規定的命名方法:
public void TestFunc(string name) { Console.WriteLine("Hello,{0}", name); }
(3)最後宣告一個委託例項:
DelegateTest dgTest = new DelegateTest(TestFunc); dgTest("Edison Chou");
(4)除錯執行可以得到以下輸出:
由上面的步湊可以看出,我們要宣告一個委託例項要為其編寫一個符合規定的命名方法。但是,如果程式中這個方法只被這個委託使用的話,總會感覺程式碼結構有點浪費。於是,微軟引入了匿名方法,使用匿名方法宣告委託,就會使程式碼結構變得簡潔,也會省去例項化的一些開銷。
2.2 引入匿名方法
(1)首先,我們來看看上面的例子如何使用匿名方法來實現:
DelegateTest dgTest2 = new DelegateTest(delegate(string name) { Console.WriteLine("Good,{0}", name); });
從執行結果圖中可以看出,原本需要傳遞方法名的地方我們直接傳遞了一個方法,這個方法以delegate(引數){方法體}的格式編寫,在{}裡邊直接寫了方法體內容。於是,我們不禁歡呼雀躍,又可以簡化一些工作量咯!
(2)其次,我們將生成的程式通過Reflector反編譯看看匿名方法是怎麼幫我們實現命名方法的效果的。
①我們可以看到,在編譯生成的類中,除了我們自己定義的方法外,還多了兩個莫名其妙的成員:
②經過一一檢視,原來編譯器幫我們生成了一個私有的委託物件以及一個私有的靜態方法。我們可以大膽猜測:原來匿名方法不是沒有名字的方法,還是生成了一個有名字的方法,只不過這個方法的名字被藏匿起來了,而且方法名是編譯器生成的。
③經過上面的分析,我們還是不甚瞭解,到底匿名方法委託物件在程式中是怎麼體現的?這裡,我們需要檢視Main方法,但是通過C#程式碼我們沒有發現一點可以幫助我們理解的。這時,我們想要刨根究底就有點麻煩了。還好,在高人指點下,我們知道可以藉助IL(中間程式碼)來分析一下。於是,在Reflector中切換展示語言,將C#改為IL,就會看到另外一番天地。
(3)由上面的分析,我們可以做出結論:編譯器對於匿名方法幫我們做了兩件事,一是生成了一個私有靜態的委託物件和一個私有靜態方法;二是將生成的方法的地址存入了委託,在執行時呼叫委託物件的Invoke方法執行該委託物件所持有的方法。因此,我們也可以看出,匿名方法需要結合委託使用。
2.3 匿名方法擴充套件
(1)匿名方法語法糖—更加簡化你的程式碼
在開發中,我們往往會採用語法糖來寫匿名方法,例如下面所示:
DelegateTest dgTest3 = delegate(string name) { Console.WriteLine("Goodbye,{0}", name); }; dgTest3("Edison Chou");
可以看出,使用該語法糖,將new DelegateTest()也去掉了。可見,編譯器讓我們越來越輕鬆了。
(2)傳參也有大學問—向方法中傳入匿名方法作為引數
①在開發中,我們往往聲明瞭一個方法,其引數是一個委託物件,可以接受任何符合委託定義的方法。
static void InvokeMethod(DelegateTest dg) { dg("Edison Chou"); }
②我們可以將已經定義的方法地址作為引數傳入InvokeMethod方法,例如:InvokeMethod(TestFunc); 當然,我們也可以使用匿名方法,不需要單獨定義就可以呼叫InvokeMethod方法。
InvokeMethod(delegate(string name) { Console.WriteLine("Fuck,{0}", name); });
(3)省略省略再省略—省略"大括號"
經過編譯器的不斷優化,我們發現連delegate後邊的()都可以省略了,我們可以看看下面一段程式碼:
InvokeMethod(delegate { Console.WriteLine("I love C sharp!"); });
而我們之前的定義是這樣的:
public delegate void DelegateTest(string testName); static void InvokeMethod(DelegateTest dg) { dg("Edison Chou"); }
我們發現定義時方法是需要傳遞一個string型別的引數的,但是我們省略了deletegate後面的括號之後就沒有引數了,那麼結果又是什麼呢?經過除錯,發現結果輸出的是:I love C sharp!
這時,我們就有點百思不得其解了!明明都沒有定義引數,為何還是滿足了符合委託定義的引數條件呢?於是,我們帶著問題還是藉助Reflector去一探究竟。
①在Main函式中,可以看到編譯器為我們自動加上了符合DelegateTest這個委託定義的方法引數,即一個string型別的字串。雖然,輸出的是I love C sharp,但它確實是符合方法定義的,因為它會接受一個string型別的引數,儘管在方法體中沒有使用到這個引數。
②剛剛在Main函式中看到了匿名方法,現在可以看看編譯器為我們所生成的命名方法。
三、擴充套件方法:[ C# 3.0/.NET 3.x 新增特性 ]
3.1 神奇—初玩擴充套件方法
(1)提到擴充套件方法,我想大部分的園友都不陌生了。不過還是來看看MSDN的定義:
MSDN 說:擴充套件方法使您能夠向現有型別“新增”方法,而無需建立新的派生型別、重新編譯或以其他方式修改原始型別。這裡的“新增”之所以使用引號,是因為並沒有真正地向指定型別新增方法。
那麼,有時候我們會問:為什麼要有擴充套件方法呢?這裡,我們可以顧名思義地想一下,擴充套件擴充套件,那麼肯定是涉及到可擴充套件性。在抽象工廠模式中,我們可以通過新增一個工廠類,而不需要更改原始碼就可以切換到新的工廠。這裡也是如此,在不修改原始碼的情況下,為某個類增加新的方法,也就實現了類的擴充套件。
(2)空說無憑,我們來看看在C#中是怎麼來判斷擴充套件方法的:通過智慧提示,我們發現有一些方法帶了一個指向下方的箭頭,檢視“溫馨提示”,我們知道他是一個擴充套件方法。所得是乃,原來我們一直對集合進行篩選的Where()方法居然是擴充套件方法而不是原生的。
我們再來看看使用Where這個擴充套件方法的程式碼示例:
static void UseExtensionMethod() { List<Person> personList = new List<Person>() { new Person(){ID=1,Name="Big Yellow",Age=10}, new Person(){ID=2,Name="Little White",Age=15}, new Person(){ID=3,Name="Middle Blue",Age=7} }; // 下面就使用了IEnumerable的擴充套件方法:Where var datas = personList.Where(delegate(Person p) { return p.Age >= 10; }); foreach (var data in datas) { Console.WriteLine("{0}-{1}-{2}", data.ID, data.Name, data.Age); } }
上述程式碼使用了Where擴充套件方法,找出集合中Age>=10的資料形成新的資料集並輸出:
(3)既然擴充套件方法是為了對類進行擴充套件,那麼我們可不可以進行自定義擴充套件呢?答案是必須可以。我們先來看看擴充套件方法是如何的定義的,可以通過剛剛的IEnumerable介面中的Where方法定義來看看有哪些規則:通過 轉到定義 的方式,我們可以看到在System.Linq名稱空間下,有叫做Enumerable的這樣一個靜態類,它的成員方法全是靜態方法,而且每個方法的大部分第一引數都是以this開頭。於是,我們可以總結出,擴充套件方法的三個要素是:靜態類、靜態方法以及this關鍵字。
public static class Enumerable { public static IEnumerable<TSource> Union<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer); }
那麼問題又來了:為何一定得是static靜態的呢?這個我們都知道靜態方法是不屬於某個類的例項的,也就是說我們不需要例項化這個類,就可以訪問這個靜態方法。所以,你懂的啦。
(4)看完擴充套件方法三要素,我們就來自動動手寫一個擴充套件方法:
public static class PersonExtension { public static string FormatOutput(this Person p) { return string.Format("ID:{0},Name:{1},Age:{2}", p.ID, p.Name, p.Age); } }
上面這個擴充套件方法完成了一個格式化輸出Person物件屬性資訊的字串構造,可以完成上面例子中的輸出效果。於是,我們可以將上面的程式碼改為以下的方式進行輸出:
static void UseMyExtensionMethod() { List<Person> personList = new List<Person>() { new Person(){ID=1,Name="Big Yellow",Age=10}, new Person(){ID=2,Name="Little White",Age=15}, new Person(){ID=3,Name="Middle Blue",Age=7} }; var datas = personList.Where(delegate(Person p) { return p.Age >= 10; }); foreach (var data in datas) { Console.WriteLine(data.FormatOutput()); } }
3.2 嗦嘎—探祕擴充套件方法
剛剛我們體驗了擴充套件方法的神奇之處,現在我們本著刨根究底的學習態度,藉助Reflector看看編譯器到底幫我們做了什麼工作?
(1)通過反編譯剛剛那個UseMyExtensionMethod方法,我們發現並沒有什麼奇怪之處。
(2)這時,我們可以將C#切換到IL程式碼看看,或許會有另一番收穫?於是,果斷切換之後,發現了真諦!
原來編譯器在編譯時自動將Person.FormatOutput更改為了PersonExtension.FormatOutput,這時我們彷彿茅塞頓開,所謂的擴充套件方法,原來就是靜態方法的呼叫而已,所德是乃(原來如此)!於是,我們可以將這樣認為:person.FormatOutput() 等同於呼叫 PersonExtension.FormatOutput(person);
(3)再檢視所編譯生成的方法,發現this關鍵已經消失了。我們不禁一聲感嘆,原來this只是一個標記而已,標記它是擴充套件的是哪一個型別,在方法體中可以對這個型別的例項進行操作。
3.3 注意—總結擴充套件方法
(1)如何定義擴充套件方法:
定義靜態類,並新增public的靜態方法,第一個引數 代表 擴充套件方法的擴充套件類。
a) 它必須放在一個非巢狀、非泛型的靜態類中(的靜態方法);
b) 它至少有一個引數;
c) 第一個引數必須附加 this 關鍵字;
d) 第一個引數不能有任何其他修飾符(out/ref)
e) 第一個引數不能是指標型別
(2)當我們把擴充套件方法定義到其它程式集中時,一定要注意呼叫擴充套件方法的環境中需要包含擴充套件方法所在的名稱空間!
(3)如果要擴充套件的類中本來就有和擴充套件方法的名稱一樣的方法,到底會呼叫成員方法還是擴充套件方法呢?
答案:編譯器預設認為一個表示式是要使用一個例項方法,但如果沒有找到,就會檢查匯入的名稱空間和當前名稱空間裡所有的擴充套件方法,並匹配到適合的方法。
參考文章
附件下載
作者:周旭龍
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連結。
相關推薦
.NET中那些所謂的新語法之二:匿名類、匿名方法與擴充套件方法
開篇:在上一篇中,我們瞭解了自動屬性、隱式型別、自動初始化器等所謂的新語法,這一篇我們繼續征程,看看匿名類、匿名方法以及常用的擴充套件方法。雖然,都是很常見的東西,但是未必我們都明白其中蘊含的奧妙。所以,跟著本篇的步伐,繼續來圍觀。 /* 新語法索引 */ 一、匿名類:[ C# 3.0/.NET 3.
.NET中那些所謂的新語法之三:系統預定義委託與Lambda表示式
開篇:在上一篇中,我們瞭解了匿名類、匿名方法與擴充套件方法等所謂的新語法,這一篇我們繼續征程,看看系統預定義委託(Action/Func/Predicate)和超愛的Lambda表示式。為了方便碼農們,.Net基類庫針對實際開發中最常用的情形提供了幾個預定義好的委託,這些委託可以直接使用,無需再重頭定義一個自
.NET中那些所謂的新語法之四:標準查詢運算子與LINQ
開篇:在上一篇中,我們瞭解了預定義委託與Lambda表示式等所謂的新語法,這一篇我們繼續征程,看看標準查詢運算子和LINQ。標準查詢運算子是定義在System.Linq.Enumerable類中的50多個為IEnumerable<T>準備的擴充套件方法,而LINQ則是一種類似於SQL風格的查詢表示
.NET中那些所謂的新語法之一:自動屬性、隱式型別、命名引數與自動初始化器
開篇:在日常的.NET開發學習中,我們往往會接觸到一些較新的語法,它們相對以前的老語法相比,做了很多的改進,簡化了很多繁雜的程式碼格式,也大大減少了我們這些菜鳥碼農的程式碼量。但是,在開心歡樂之餘,我們也不禁地對編譯器內部到底為我們做了哪些事兒而感到好奇?於是,我們就藉助反編譯神器,去看看編譯器到底做了啥事!
ALSA音效卡驅動中的DAPM詳解之二:widget-具備路徑和電源管理資訊的kcontrol
上一篇文章中,我們介紹了音訊驅動中對基本控制單元的封裝:kcontrol。利用kcontrol,我們可以完成對音訊系統中的mixer,mux,音量控制,音效控制,以及各種開關量的控制,通過對各種kcontrol的控制,使得音訊硬體能夠按照我們預想的結果進行工作。同時我
Spring 3.1新特性之二:@Enable*註解的原始碼,spring原始碼分析之定時任務Scheduled註解
分析SpringBoot的自動化配置原理的時候,可以觀察下這些@Enable*註解的原始碼,可以發現所有的註解都有一個@Import註解。@Import註解是用來匯入配置類的,這也就是說這些自動開啟的實現其實是匯入了一些自動配置的Bean。 如:freemarker的自動化配置類FreeMarkerAuto
Java8新特性之二:方法引用
輸出結果 知識 public ava urn strong class rules ros 上一節介紹了Java8新特性中的Lambda表達式,本小節繼續講解Java8的新特性之二:方法引用。方法引用其實也離不開Lambda表達式。 1、方法引用的使用場景 我們
selenium與頁面交互之二:webelement類的屬性
tex tro 錯誤信息 類的屬性 大小 html標簽 cnblogs text ron webelement類的屬性如下: element.size() 獲取元素的大小 element.tag_name() 獲取元素的HTML標簽名稱 element.text()
【深入Java虛擬機】之二:Class類文件結構
本質 拒絕 處理 implement align 默認值 改變 占用 至少 平臺無關性 Java是與平臺無關的語言,這得益於Java源代碼編譯後生成的存儲字節碼的文件,即Class文件,以及Java虛擬機的實現。不僅使用Java編譯器可以把Java代碼編譯成存儲字節
移動端適配之二:visual viewport、layout viewport和ideal viewport介紹
上一篇博文,可算把畫素這個東西講清楚了。在這篇博文裡面,將繼續介紹viewport相關的內容。 很多部落格都會提到PPK所講的三個viewport,有的講的比較複雜,看的雲裡霧裡,我這裡也大概介紹一下,三個viewport主要是相對於移動端而言的。 visual viewport
C#.NET:高階程式設計之匿名類、匿名方法與擴充套件方法
[文中插圖丟失,推薦檢視原文]!important 開篇:在上一篇中,我們瞭解了自動屬性、隱式型別、自動初始化器等所謂的新語法,這一篇我們繼續征程,看看匿名類、匿名方法以及常用的擴充套件方法。雖然,都是很常見的東西,但是未必我們都明白其中蘊含的奧妙。所以,跟著本篇的
Tomcat學習之二:tomcat安裝、配置及目錄檔案說明
一、下載JDK和Tomcat 二、安裝JDK 點選JDK應用程式預設安裝即可,記下JDK的安裝目錄(例如:C:\Program Files\Java\jdk1.7.0_45)。 三、配置JDK和Tomcat 1. 配置JDK
Hexo系列教程之二:購買域名、設定DNS
前言 因為部落格託管在github,所以個人部落格地址是github的二級域名,不容易讓人記住,也很難讓百度收錄,所以很多人都自己註冊域名,和部落格地址繫結,這樣只要輸入自己申請的域名,就能跳轉到部落格首頁,也算是真正擁有了個人網站了。g
【深入Java虛擬機器】之二:Class類檔案結構
平臺無關性 Java是與平臺無關的語言,這得益於Java原始碼編譯後生成的儲存位元組碼的檔案,即Class檔案,以及Java虛擬機器的實現。不僅使用Java編譯器可以把Java程式碼編譯成儲存位元組碼的Class檔案,使用JRuby等其他語言的編譯器也可以把程式
Flink處理函式實戰之二:ProcessFunction類
### 歡迎訪問我的GitHub [https://github.com/zq2599/blog_demos](https://github.com/zq2599/blog_demos) 內容:所有原創文章分類彙總及配套原始碼,涉及Java、Docker、Kubernetes、DevOPS等; ###
Java中的內部類、匿名類、匿名內部類
內部類 Java中在一個類的內部定義的類叫做內部類(inner class)。建立一個內部類時,其物件就擁有了與外部類物件之間的關係。這種通過this來引用形成的,是內部類物件可以隨意訪問外部類中的所有成員變數!(因為被private修飾的成員變數和成員方法僅能被該類所使用,內部類中可以
Java之路:截尾、舍入與提升
截尾和舍入 在執行窄化轉換時,必須注意截尾與舍入的問題。例如:如果將一個浮點數轉換為整型值,Java會如何處理呢?如果將29.7轉換為int,結果是30還是29? public class Cast { public static void main(String[] args)
區塊鏈鼻祖比特幣之9:挖礦、礦池與比特幣的產生
挖礦 前面我們已經提到過比特幣如何通過數字簽名和交易鏈轉移,交易順序又是如何受到區塊鏈保護。那麼你可能就會有疑問,想付款,你必須參照到先前的支付,那麼比特幣又是怎麼產生的呢?一個緩慢,隨機並讓比特幣流通的方式是解開區塊者會得到比特幣作為獎賞,這既是為什麼解開區塊被
【只怕沒有幾個人能說清楚】系列之二:Unity中的特殊文件夾
物體 avi ebp time 編輯模式 tro hive 預覽 打包 參考:http://www.manew.com/thread-99292-1-1.html 1. 隱藏文件夾 以.開頭的文件夾會被忽略。在這種文件夾中的資源不會被導入,腳本不會被編譯。也不會出現
ALERT日誌中常見監聽相關報錯之二:ORA-3136錯誤的排查
hang gui tns -c 未在 fatal odr bound 問題 最近在多個大型系統中遇到此問題,一般來說假設client未反映異常的話能夠忽略的。 假設是client登陸時遇到ORA-12170: TNS:Co