泛型(Generics) 泛型是CLR 2.0中引入的最重要的新特性,,使得可以在類(lèi),、方法中對(duì)使用的類(lèi)型進(jìn)行參數(shù)化,。 例如,,這里定義了一個(gè)泛型類(lèi):
class MyCollection<T> { T variable1; private void Add(T param) { } }
使用的時(shí)候: MyCollection<string> list2 = new MyCollection<string>();
泛型的好處
泛型方法,、泛型委托、泛型接口 除了泛型類(lèi)之外,,還有泛型方法,、泛型委托、泛型接口: //泛型委托 public static delegate T1 MyDelegate<T1, T2>(T2 item);
泛型約束(constraints) 可以給泛型的類(lèi)型參數(shù)上加約束,,可以要求這些類(lèi)型參數(shù)滿(mǎn)足一定的條件
這些約束,可以同時(shí)一起使用: class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
default 關(guān)鍵字 這個(gè)關(guān)鍵可以使用在類(lèi)型參數(shù)上: default(T); 對(duì)于值類(lèi)型,,返回0,,引用類(lèi)型,返回null,,對(duì)于結(jié)構(gòu)類(lèi)型,,會(huì)返回一個(gè)成員值全部為0的結(jié)構(gòu)實(shí)例。 迭代器(iterator) 可以在不實(shí)現(xiàn)IEnumerable就能使用foreach語(yǔ)句,,在編譯器碰到y(tǒng)ield return時(shí),,它會(huì)自動(dòng)生成IEnumerable 接口的方法。在實(shí)現(xiàn)迭代器的方法或?qū)傩灾?,返回?lèi)型必須是IEnumerable, IEnumerator, IEnumerable<T>,,或 IEnumerator<T>,。迭代器使得遍歷一些零碎數(shù)據(jù)的時(shí)候很方便,不用去實(shí)現(xiàn)Current, MoveNext 這些方法,。 public System.Collections.IEnumerator GetEnumerator() { yield return -1; for (int i = 1; i < max; i++) { yield return i; } } 可空類(lèi)型(Nullable Type) 可空類(lèi)型System.Nullable<T>,,可空類(lèi)型僅針對(duì)于值類(lèi)型,不能針對(duì)引用類(lèi)型去創(chuàng)建,。System.Nullable<T>簡(jiǎn)寫(xiě)為T(mén) ?,。 int? num = null; if (num.HasValue == true) { System.Console.WriteLine("num = " + num.Value); } else { System.Console.WriteLine("num = Null"); } 如果HasValue為false,那么在使用value值的時(shí)候會(huì)拋出異常,。把一個(gè)Nullable的變量x賦值給一個(gè)非Nullable的變量y可以這么寫(xiě): int y = x ?? -1; 匿名方法(Anonymous Method) 在C#2.0之前,,給只能用一個(gè)已經(jīng)申明好的方法去創(chuàng)建一個(gè)委托。有了匿名方法后,,可以在創(chuàng)建委托的時(shí)候直接傳一個(gè)代碼塊過(guò)去,。 delegate void Del(int x); Del d = delegate(int k) { /* ... */ }; System.Threading.Thread t1 = new System.Threading.Thread (delegate() { System.Console.Write("Hello, "); } ); 委托語(yǔ)法的簡(jiǎn)化// C# 1.0的寫(xiě)法 ThreadStart ts1 = new ThreadStart(Method1); // C# 2.0可以這么寫(xiě) ThreadStart ts2 = Method1; 委托的協(xié)變和逆變(covariance and contravariance) 有下面的兩個(gè)類(lèi): class Parent { } class Child: Parent { } 然后看下面的兩個(gè)委托: public delegate Parent DelgParent(); public delegate Child DelgChild(); public static Parent Method1() { return null; } public static Child Method2() { return null; } static void Main() { DelgParent del1= Method1; DelgChild del2= Method2; del1 = del2; } 注意上面的,DelgParent 和DelgChild 是完全不同的類(lèi)型,,他們之間本身沒(méi)有任何的繼承關(guān)系,,所以理論上來(lái)說(shuō)他們是不能相互賦值的。但是因?yàn)閰f(xié)變的關(guān)系,,使得我們可以把DelgChild類(lèi)型的委托賦值給DelgParent 類(lèi)型的委托,。協(xié)變針對(duì)委托的返回值,,逆變針對(duì)參數(shù),,原理是一樣的。 部分類(lèi)(partial) 在申明一個(gè)類(lèi),、結(jié)構(gòu)或者接口的時(shí)候,,用partial關(guān)鍵字,可以讓源代碼分布在不同的文件中,。我覺(jué)得這個(gè)東西完全是為了照顧Asp.net代碼分離而引入的功能,,真沒(méi)什么太大的實(shí)際用處。微軟說(shuō)在一些大工程中可以把類(lèi)分開(kāi)在不同的文件中讓不同的人去實(shí)現(xiàn),,方便團(tuán)隊(duì)協(xié)作,,這個(gè)我覺(jué)得純屬胡扯。 部分類(lèi)僅是編譯器提供的功能,,在編譯的時(shí)候會(huì)把partial關(guān)鍵字定義的類(lèi)和在一起去編譯,,和CRL沒(méi)什么關(guān)系。 靜態(tài)類(lèi)(static class) 靜態(tài)類(lèi)就一個(gè)只能有靜態(tài)成員的類(lèi),,用static關(guān)鍵字對(duì)類(lèi)進(jìn)行標(biāo)示,,靜態(tài)類(lèi)不能被實(shí)例化。靜態(tài)類(lèi)理論上相當(dāng)于一個(gè)只有靜態(tài)成員并且構(gòu)造函數(shù)為私有的普通類(lèi),,靜態(tài)類(lèi)相對(duì)來(lái)說(shuō)的好處就是,,編譯器能夠保證靜態(tài)類(lèi)不會(huì)添加任何非靜態(tài)成員,。 global:: 這個(gè)代表了全局命名空間(最上層的命名空間),也就是任何一個(gè)程序的默認(rèn)命名空間,。 class TestApp { public class System { } const int Console = 7; static void Main() { //用這個(gè)訪(fǎng)問(wèn)就會(huì)出錯(cuò),,System和Console都被占用了 //Console.WriteLine(number); global::System.Console.WriteLine(number); } } extern alias 用來(lái)消除不同程序集中類(lèi)名重復(fù)的沖突,這樣可以引用同一個(gè)程序集的不同版本,,也就是說(shuō)在編譯的時(shí)候,,提供了一個(gè)將有沖突的程序集進(jìn)行區(qū)分的手段。 在編譯的時(shí)候,,使用命令行參數(shù)來(lái)指明alias,,例如: /r:aliasName=assembly1.dll 在Visual Studio里面,在被引用的程序集的屬性里面可以指定Alias的值,,默認(rèn)是global,。 然后在代碼里面就可以使用了: extern alias aliasName; //這行需要在using這些語(yǔ)句的前面 using System; using System.Collections.Generic; using System.Text; using aliasName.XXX; 屬性Accessor訪(fǎng)問(wèn)控制 public virtual int TestProperty { protected set { } get { return 0; } } 友元程序集(Friend Assembly) 可以讓其它程序集訪(fǎng)問(wèn)自己的internal成員(private的還是不行),使用Attributes來(lái)實(shí)現(xiàn),,例如: [assembly:InternalsVisibleTo("cs_friend_assemblies_2")] 注意這個(gè)作用范圍是整個(gè)程序集,。 fixed關(guān)鍵字 可以使用fixed關(guān)鍵字來(lái)創(chuàng)建固定長(zhǎng)度的數(shù)組,但是數(shù)組只能是bool, byte, char, short, int, long, sbyte, ushort, uint, ulong, float, double中的一種,。 這主要是為了更好的處理一些非托管的代碼,。比如下面的這個(gè)結(jié)構(gòu)體: public struct MyArray { public fixed char pathName[128]; } 如果不用fixed的話(huà),無(wú)法預(yù)先占住128個(gè)char的空間,,使用fixed后可以很好的和非托管代碼進(jìn)行交互,。 volatile關(guān)鍵字 用來(lái)表示相關(guān)的字可能被多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn),編譯器不會(huì)對(duì)相應(yīng)的值做針對(duì)單線(xiàn)程下的優(yōu)化,,保證相關(guān)的值在任何時(shí)候訪(fǎng)問(wèn)都是最新的,。 #pragma warning 用來(lái)取消或者添加編譯時(shí)的警告信息。每個(gè)警告信息都會(huì)有個(gè)編號(hào),,如果warning CS01016之類(lèi)的,,使用的時(shí)候取CS后面的那個(gè)數(shù)字,例如: #pragma warning disable 414, 3021 這樣CS414和CS3021的警告信息就都不會(huì)顯示了,。 C# 3.0類(lèi)型推斷 申明變量的時(shí)候,,可以不用直指定類(lèi)型: var i = 5; var s = "Hello"; //兩種寫(xiě)法是一樣的 int i = 5; string s = "Hello"; 類(lèi)型推斷也支持?jǐn)?shù)組: var b = new[] { 1, 1.5, 2, 2.5 }; // double[] var c = new[] { "hello", null, "world” }; // string[] 擴(kuò)展方法 擴(kuò)展方法必須被定義在靜態(tài)類(lèi)中,并且必須是非泛型,、非嵌套的靜態(tài)類(lèi),。例如: public static class JeffClass { public static int StrToInt32(this string s) { return Int32.Parse(s); } public static T[] SomeMethd<T>(this T[] source, int pram1, int pram2) { /**/ } } 上面一個(gè)是給string類(lèi)型的對(duì)象添加了一個(gè)方法,另一個(gè)是給所有類(lèi)型的數(shù)組添加了一個(gè)方法,,方法有兩個(gè)整型參數(shù),。 擴(kuò)展方法只在當(dāng)前的命名空間類(lèi)有效,如果所在命名空間被其它命名空間import引用了,那么在其它命名空間中也有效,。擴(kuò)展方法的優(yōu)先級(jí)低于其它的常規(guī)方法,,也就是說(shuō)如果擴(kuò)展方法與其它的方法相同,那么擴(kuò)展方法不會(huì)被調(diào)用,。 Lamda表達(dá)式 可以看成是對(duì)匿名方法的一個(gè)語(yǔ)法上的簡(jiǎn)化,,但是λ表達(dá)式同時(shí)可以裝換為表達(dá)式樹(shù)類(lèi)型。 對(duì)象和集合的初始化 var contacts = new List<Contact> { new Contact { Name = "Chris", PhoneNumbers = { "123455", "6688" } }, new Contact { Name = "Jeffrey", PhoneNumbers = { "112233" } } }; 匿名類(lèi)型 var p1 = new { Name = "Lawnmower", Price = 495.00 }; var p2 = new { Name = "Shovel", Price = 26.95 }; p1 = p2; 自動(dòng)屬性 會(huì)自動(dòng)生成一個(gè)后臺(tái)的私有變量 public Class Point { public int X { get; set; } public int Y { get; set; } } 查詢(xún)表達(dá)式 這個(gè)其實(shí)就是擴(kuò)展方法的運(yùn)用,,編譯器提供了相關(guān)的語(yǔ)法便利,,下面兩端代碼是等價(jià)的: from g in from c in customers group c by c.Country select new { Country = g.Key, CustCount = g.Count() } customers. GroupBy(c => c.Country). Select(g => new { Country = g.Key, CustCount = g.Count() }) 表達(dá)式樹(shù) Func<int,int> f = x => x + 1; Expression<Func<int,int>> e = x => x + 1; C# 4.0協(xié)變和逆變 這個(gè)在C#2.0中就已經(jīng)支持委托的協(xié)變和逆變了,C#4.0開(kāi)始支持針對(duì)泛型接口的協(xié)變和逆變: IList<string> strings = new List<string>(); IList<object> objects = strings; 協(xié)變和逆變僅針對(duì)引用類(lèi)型,。 動(dòng)態(tài)綁定 看例子: class BaseClass { public void print() { Console.WriteLine(); } } Object o = new BaseClass(); dynamic a = o; //這里可以調(diào)用print方法,,在運(yùn)行時(shí)a會(huì)知道自己是個(gè)什么類(lèi)型。 這里的缺點(diǎn)在于編譯的時(shí)候無(wú)法檢查方法的合法性,,寫(xiě)錯(cuò)的話(huà)就會(huì)出運(yùn)行時(shí)錯(cuò)誤,。 a.print(); 可選參數(shù),命名參數(shù) private void CreateNewStudent(string name, int studentid = 0, int year = 1) 這樣,,最后一個(gè)參數(shù)不給的話(huà)默認(rèn)值就是1,,提供這個(gè)特性可以免去寫(xiě)一些重載方法的麻煩。 調(diào)用方法的時(shí)候,,可以指定參數(shù)的名字來(lái)給值,,不用按照方法參數(shù)的順序來(lái)制定參數(shù)值: CreateNewStudent(year:2, name:"Hima", studentid: 4); //沒(méi)有按照方法定義的參數(shù)順序 C# 5.01. 異步編程 在.Net 4.5中,通過(guò)async和await兩個(gè)關(guān)鍵字,,引入了一種新的基于任務(wù)的異步編程模型(TAP),。在這種方式下,可以通過(guò)類(lèi)似同步方式編寫(xiě)異步代碼,,極大簡(jiǎn)化了異步編程模型,。如下式一個(gè)簡(jiǎn)單的實(shí)例: static async void DownloadStringAsync2(Uri uri) 而之前的方式是這樣的: static void DownloadStringAsync(Uri uri) 也許前面這個(gè)例子不足以體現(xiàn)async和await帶來(lái)的優(yōu)越性,下面這個(gè)例子就明顯多了: public void CopyToAsyncTheHardWay(Stream source, Stream destination) 關(guān)于基于任務(wù)的異步編程模型需要介紹的地方還比較多,,不是一兩句能說(shuō)完的,有空的話(huà)后面再專(zhuān)門(mén)寫(xiě)篇文章來(lái)詳細(xì)介紹下,。另外也可參看微軟的官方網(wǎng)站:Visual Studio Asynchronous Programming,,其官方文檔Task-Based Asynchronous Pattern Overview介紹的非常詳細(xì), VisualStudio中自帶的CSharp Language Specification中也有一些說(shuō)明,。 2. 調(diào)用方信息 很多時(shí)候,,我們需要在運(yùn)行過(guò)程中記錄一些調(diào)測(cè)的日志信息,如下所示: public void DoProcessing() 為了調(diào)測(cè)方便,,除了事件信息外,,我們往往還需要知道發(fā)生該事件的代碼位置以及調(diào)用棧信息。在C++中,我們可以通過(guò)定義一個(gè)宏,,然后再宏中通過(guò)__FILE__和__LINE__來(lái)獲取當(dāng)前代碼的位置,,但C#并不支持宏,往往只能通過(guò)StackTrace來(lái)實(shí)現(xiàn)這一功能,,但StackTrace卻有不是很靠譜,,常常獲取不了我們所要的結(jié)果。 針對(duì)這個(gè)問(wèn)題,,在.Net 4.5中引入了三個(gè)Attribute:CallerMemberName,、CallerFilePath和CallerLineNumber。在編譯器的配合下,,分別可以獲取到調(diào)用函數(shù)(準(zhǔn)確講應(yīng)該是成員)名稱(chēng),,調(diào)用文件及調(diào)用行號(hào)。上面的TraceMessage函數(shù)可以實(shí)現(xiàn)如下: public void TraceMessage(string message, 另外,,在構(gòu)造函數(shù),,析構(gòu)函數(shù)、屬性等特殊的地方調(diào)用CallerMemberName屬性所標(biāo)記的函數(shù)時(shí),,獲取的值有所不同,,其取值如下表所示:
例如,,對(duì)于在屬性中調(diào)用CallerMemberName所標(biāo)記的函數(shù)即可獲取屬性名稱(chēng),通過(guò)這種方式可以簡(jiǎn)化 INotifyPropertyChanged 接口的實(shí)現(xiàn),。 C# 6.01,、自動(dòng)屬性的增強(qiáng) 1.1、自動(dòng)屬性初始化 (Initializers for auto-properties) C#4.0下的果斷實(shí)現(xiàn)不了的,。 C#6.0中自動(dòng)屬性的初始化方式 只要接觸過(guò)C#的肯定都會(huì)喜歡這種方式,。真是簡(jiǎn)潔方便呀。
1.2,、只讀屬性初始化Getter-only auto-properties 先來(lái)看一下我們之前使用的方式吧 public class Customer { public string Name { get; } public Customer(string firstName,string lastName) { Name = firstName +" "+ lastName; } } 再來(lái)看一下C#6.0中 public class Customer { public string FirstName { get; }="aehyok"; public string LastName { get; }="Kris"; } 和第一條自動(dòng)屬性初始化使用方式一致,。 2、Expression bodied function members 2.1 用Lambda作為函數(shù)體Expression bodies on method-like members public Point Move(int dx, int dy) => new Point(x + dx, y + dy); 再來(lái)舉一個(gè)簡(jiǎn)單的例子:一個(gè)沒(méi)有返回值的函數(shù) public void Print() => Console.WriteLine(FirstName + " " + LastName);
2.2,、Lambda表達(dá)式用作屬性Expression bodies on property-like function members public override string ToString() { return FirstName + " " + LastName; } 現(xiàn)在C#6中 public class User { public string FirstName { get; set; } public string LastName { get; set; } public override string ToString() => string.Format("{0}——{1}", FirstName, LastName); public string FullName => FirstName + " " + LastName; }
3,、引用靜態(tài)類(lèi)Using Static 在Using中可以指定一個(gè)靜態(tài)類(lèi),然后可以在隨后的代碼中直接使用靜態(tài)的成員
4,、空值判斷Null-conditional operators 直接來(lái)看代碼和運(yùn)行結(jié)果 通過(guò)結(jié)果可以發(fā)現(xiàn)返回的都為null,再也不像以前那樣繁瑣的判斷null勒,。
5,、字符串嵌入值 在字符串中嵌入值 之前一直使用的方式是 現(xiàn)在我們可以簡(jiǎn)單的通過(guò)如下的方式進(jìn)行拼接 6、nameof表達(dá)式nameof expressions 在方法參數(shù)檢查時(shí),,你可能經(jīng)??吹竭@樣的代碼(之前用的少,這次也算學(xué)到了) public static void AddCustomer(Customer customer) { if (customer == null) { throw new ArgumentNullException("customer"); } } 里面有那個(gè)customer是我們手寫(xiě)的字符串,,在給customer改名時(shí),,很容易把下面的那個(gè)字符串忘掉,C#6.0 nameof幫我們解決了這個(gè)問(wèn)題,,看看新寫(xiě)法 public static void AddCustomer(Customer customer) { if (customer == null) { throw new ArgumentNullException(nameof(customer)); } }
7,、帶索引的對(duì)象初始化器Index initializers 直接通過(guò)索引進(jìn)行對(duì)象的初始化,原來(lái)真的可以實(shí)現(xiàn) 通過(guò)這種方式可以發(fā)現(xiàn)字典中只有三個(gè)元素,,所以也就只有這三個(gè)索引可以訪(fǎng)問(wèn)額,,其他類(lèi)型的對(duì)象和集合也是可以通過(guò)這種方式進(jìn)行初始化的,在此就不進(jìn)行一一列舉了,。 8,、異常過(guò)濾器 (Exception filters) 先來(lái)看一個(gè)移植過(guò)來(lái)的方法 try { var numbers = new Dictionary<int, string> {[7] = "seven",[9] = "nine",[13] = "thirteen" }; } catch (ArgumentNullException e) { if (e.ParamName == "customer") { Console.WriteLine("customer can not be null"); } } 在微軟的文檔中還給出了另一種用法,這個(gè)異常會(huì)在日志記錄失敗時(shí)拋給上一層調(diào)用者 private static bool Log(Exception e) { ///處理一些日志 return false; } static void Main(string[] args) { try { /// } catch (Exception e){if (!Log(e)) { } } Console.ReadLine(); }
9,、catch和finally 中的 await —— Await in catch and finally blocks 在C#5.0中,,await關(guān)鍵字是不能出現(xiàn)在catch和finnaly塊中的。而在6.0中 try { res = await Resource.OpenAsync(…); // You could do this. … } catch (ResourceException e) { await Resource.LogAsync(res, e); // Now you can do this … } finally { if (res != null) await res.CloseAsync(); // … and this. }
10,、無(wú)參數(shù)的結(jié)構(gòu)體構(gòu)造函數(shù)—— Parameterless constructors in structs
C# 7.01. out 變量(out variables)以前我們使用out變量必須在使用前進(jìn)行聲明,,C# 7.0 給我們提供了一種更簡(jiǎn)潔的語(yǔ)法 “使用時(shí)進(jìn)行內(nèi)聯(lián)聲明” 。如下所示: 1 var input = ReadLine(); 2 if (int.TryParse(input, out var result)) 3 { 4 WriteLine("您輸入的數(shù)字是:{0}",result); 5 } 6 else 7 { 8 WriteLine("無(wú)法解析輸入..."); 9 } 上面代碼編譯后: 1 int num; 2 string s = Console.ReadLine(); 3 if (int.TryParse(s, out num)) 4 { 5 Console.WriteLine("您輸入的數(shù)字是:{0}", num); 6 } 7 else 8 { 9 Console.WriteLine("無(wú)法解析輸入..."); 10 } 原理解析:所謂的 “內(nèi)聯(lián)聲明” 編譯后就是以前的原始寫(xiě)法,,只是現(xiàn)在由編譯器來(lái)完成,。 備注:在進(jìn)行內(nèi)聯(lián)聲明時(shí),即可直接寫(xiě)明變量的類(lèi)型也可以寫(xiě)隱式類(lèi)型,,因?yàn)閛ut關(guān)鍵字修飾的一定是局部變量,。 2. 元組(Tuples)元組(Tuple)在 .Net 4.0 的時(shí)候就有了,但元組也有些缺點(diǎn),,如: 1)Tuple 會(huì)影響代碼的可讀性,,因?yàn)樗膶傩悦际牵篒tem1,Item2.. ,。 2)Tuple 還不夠輕量級(jí),,因?yàn)樗且妙?lèi)型(Class)。 備注:上述所指 Tuple 還不夠輕量級(jí),,是從某種意義上來(lái)說(shuō)的或者是一種假設(shè),即假設(shè)分配操作非常的多,。 C# 7 中的元組(ValueTuple)解決了上述兩個(gè)缺點(diǎn): 1)ValueTuple 支持語(yǔ)義上的字段命名,。 2)ValueTuple 是值類(lèi)型(Struct)。 1. 如何創(chuàng)建一個(gè)元組? 1 var tuple = (1, 2); // 使用語(yǔ)法糖創(chuàng)建元組 2 var tuple2 = ValueTuple.Create(1, 2); // 使用靜態(tài)方法【Create】創(chuàng)建元組 3 var tuple3 = new ValueTuple<int, int>(1, 2); // 使用 new 運(yùn)算符創(chuàng)建元組 4 5 WriteLine($"first:{tuple.Item1}, second:{tuple.Item2}, 上面三種方式都是等價(jià)的,。"); 原理解析:上面三種方式最終都是使用 new 運(yùn)算符來(lái)創(chuàng)建實(shí)例,。 2. 如何創(chuàng)建給字段命名的元組? 1 // 左邊指定字段名稱(chēng) 2 (int one, int two) tuple = (1, 2); 3 WriteLine($"first:{tuple.one}, second:{tuple.two}"); 4 5 // 右邊指定字段名稱(chēng) 6 var tuple2 = (one: 1, two: 2); 7 WriteLine($"first:{tuple2.one}, second:{tuple2.two}"); 8 9 // 左右兩邊同時(shí)指定字段名稱(chēng) 10 (int one, int two) tuple3 = (first: 1, second: 2); /* 此處會(huì)有警告:由于目標(biāo)類(lèi)型(xx)已指定了其它名稱(chēng),,因?yàn)楹雎栽M名稱(chēng)xxx */ 11 WriteLine($"first:{tuple3.one}, second:{tuple3.two}"); 注:左右兩邊同時(shí)指定字段名稱(chēng),,會(huì)使用左邊的字段名稱(chēng)覆蓋右邊的字段名稱(chēng)(一一對(duì)應(yīng))。 原理解析:上述給字段命名的元組在編譯后其字段名稱(chēng)還是:Item1, Item2...,,即:“命名”只是語(yǔ)義上的命名,。 3. 什么是解構(gòu)? 解構(gòu)顧名思義就是將整體分解成部分,。 4. 解構(gòu)元組,,如下所示: 1 var (one, two) = GetTuple(); 2 3 WriteLine($"first:{one}, second:{two}"); 1 static (int, int) GetTuple() 2 { 3 return (1, 2); 4 } 原理解析:解構(gòu)元組就是將元組中的字段值賦值給聲明的局部變量(編譯后可查看)。 備注:在解構(gòu)時(shí)“=”左邊能提取變量的數(shù)據(jù)類(lèi)型(如上所示),,元組中字段類(lèi)型相同時(shí)即可提取具體類(lèi)型也可以是隱式類(lèi)型,,但元組中字段類(lèi)型 不相同時(shí)只能提取隱式類(lèi)型。 5. 解構(gòu)可以應(yīng)用于 .Net 的任意類(lèi)型,,但需要編寫(xiě) Deconstruct 方法成員(實(shí)例或擴(kuò)展),。如下所示: 1 public class Student 2 { 3 public Student(string name, int age) 4 { 5 Name = name; 6 Age = age; 7 } 8 9 public string Name { get; set; } 10 11 public int Age { get; set; } 12 13 public void Deconstruct(out string name, out int age) 14 { 15 name = Name; 16 age = Age; 17 } 18 } 使用方式如下: 1 var (Name, Age) = new Student("Mike", 30); 2 3 WriteLine($"name:{Name}, age:{Age}"); 原理解析:編譯后就是由其實(shí)例調(diào)用 Deconstruct 方法,然后給局部變量賦值,。 Deconstruct 方法簽名: 1 // 實(shí)例簽名 2 public void Deconstruct(out type variable1, out type variable2...) 3 4 // 擴(kuò)展簽名 5 public static void Deconstruct(this type instance, out type variable1, out type variable2...) 總結(jié):1. 元組的原理是利用了成員類(lèi)型的嵌套或者是說(shuō)成員類(lèi)型的遞歸,。2. 編譯器很牛B才能提供如此優(yōu)美的語(yǔ)法。 使用 ValueTuple 則需要導(dǎo)入: Install - Package System.ValueTuple 3. 模式匹配(Pattern matching)1. is 表達(dá)式(is expressions),,如: 1 static int GetSum(IEnumerable<object> values) 2 { 3 var sum = 0; 4 if (values == null) return sum; 5 6 foreach (var item in values) 7 { 8 if (item is short) // C# 7 之前的 is expressions 9 { 10 sum += (short)item; 11 } 12 else if (item is int val) // C# 7 的 is expressions 13 { 14 sum += val; 15 } 16 else if (item is string str && int.TryParse(str, out var result)) // is expressions 和 out variables 結(jié)合使用 17 { 18 sum += result; 19 } 20 else if (item is IEnumerable<object> subList) 21 { 22 sum += GetSum(subList); 23 } 24 } 25 26 return sum; 27 } 使用方法: 1 條件控制語(yǔ)句(obj is type variable) 2 { 3 // Processing... 4 } 原理解析:此 is 非彼 is ,,這個(gè)擴(kuò)展的 is 其實(shí)是 as 和 if 的組合。即它先進(jìn)行 as 轉(zhuǎn)換再進(jìn)行 if 判斷,,判斷其結(jié)果是否為 null,,不等于 null 則執(zhí)行 語(yǔ)句塊邏輯,反之不行,。由上可知其實(shí)C# 7之前我們也可實(shí)現(xiàn)類(lèi)似的功能,,只是寫(xiě)法上比較繁瑣。 2. switch語(yǔ)句更新(switch statement updates),,如: 1 static int GetSum(IEnumerable<object> values) 2 { 3 var sum = 0; 4 if (values == null) return 0; 5 6 foreach (var item in values) 7 { 8 switch (item) 9 { 10 case 0: // 常量模式匹配 11 break; 12 case short sval: // 類(lèi)型模式匹配 13 sum += sval; 14 break; 15 case int ival: 16 sum += ival; 17 break; 18 case string str when int.TryParse(str, out var result): // 類(lèi)型模式匹配 + 條件表達(dá)式 19 sum += result; 20 break; 21 case IEnumerable<object> subList when subList.Any(): 22 sum += GetSum(subList); 23 break; 24 default: 25 throw new InvalidOperationException("未知的類(lèi)型"); 26 } 27 } 28 29 return sum; 30 } 使用方法: 1 switch (item) 2 { 3 case type variable1: 4 // processing... 5 break; 6 case type variable2 when predicate: 7 // processing... 8 break; 9 default: 10 // processing... 11 break; 12 } 原理解析:此 switch 非彼 switch,,編譯后你會(huì)發(fā)現(xiàn)擴(kuò)展的 switch 就是 as 、if ,、goto 語(yǔ)句的組合體,。同 is expressions 一樣,以前我們也能實(shí) 現(xiàn)只是寫(xiě)法比較繁瑣并且可讀性不強(qiáng),。 總結(jié):模式匹配語(yǔ)法是想讓我們?cè)诤?jiǎn)單的情況下實(shí)現(xiàn)類(lèi)似與多態(tài)一樣的動(dòng)態(tài)調(diào)用,,即在運(yùn)行時(shí)確定成員類(lèi)型和調(diào)用具體的實(shí)現(xiàn),。 4. 局部引用和引用返回 (Ref locals and returns)我們知道 C# 的 ref 和 out 關(guān)鍵字是對(duì)值傳遞的一個(gè)補(bǔ)充,是為了防止值類(lèi)型大對(duì)象在Copy過(guò)程中損失更多的性能?,F(xiàn)在在C# 7中 ref 關(guān)鍵字得 到了加強(qiáng),,它不僅可以獲取值類(lèi)型的引用而且還可以獲取某個(gè)變量(引用類(lèi)型)的局部引用。如: 1 static ref int GetLocalRef(int[,] arr, Func<int, bool> func) 2 { 3 for (int i = 0; i < arr.GetLength(0); i++) 4 { 5 for (int j = 0; j < arr.GetLength(1); j++) 6 { 7 if (func(arr[i, j])) 8 { 9 return ref arr[i, j]; 10 } 11 } 12 } 13 14 throw new InvalidOperationException("Not found"); 15 } Call: 1 int[,] arr = { { 10, 15 }, { 20, 25 } }; 2 ref var num = ref GetLocalRef(arr, c => c == 20); 3 num = 600; 4 5 Console.WriteLine(arr[1, 0]); Print results:
使用方法: 1. 方法的返回值必須是引用返回: a) 聲明方法簽名時(shí)必須在返回類(lèi)型前加上 ref 修飾,。 b) 在每個(gè) return 關(guān)鍵字后也要加上 ref 修飾,,以表明是返回引用。 2. 分配引用(即賦值),,必須在聲明局部變量前加上 ref 修飾,,以及在方法返回引用前加上 ref 修飾。 注:C# 開(kāi)發(fā)的是托管代碼,,所以一般不希望程序員去操作指針,。并由上述可知在使用過(guò)程中需要大量的使用 ref 來(lái)標(biāo)明這是引用變量(編譯后其 實(shí)沒(méi)那么多),當(dāng)然這也是為了提高代碼的可讀性,。 總結(jié):雖然 C# 7 中提供了局部引用和引用返回,,但為了防止濫用所以也有諸多約束,如: 1. 你不能將一個(gè)值分配給 ref 變量,,如: 1 ref int num = 10; // error:無(wú)法使用值初始化按引用變量 2. 你不能返回一個(gè)生存期不超過(guò)方法作用域的變量引用,,如: 1 public ref int GetLocalRef(int num) => ref num; // error: 無(wú)法按引用返回參數(shù),因?yàn)樗皇?ref 或 out 參數(shù) 3. ref 不能修飾 “屬性” 和 “索引器”,。 1 var list = new List<int>(); 2 ref var n = ref list.Count; // error: 屬性或索引器不能作為 out 或 ref 參數(shù)傳遞 原理解析:非常簡(jiǎn)單就是指針傳遞,,并且個(gè)人覺(jué)得此語(yǔ)法的使用場(chǎng)景非常有限,都是用來(lái)處理大對(duì)象的,,目的是減少GC提高性能,。 5. 局部函數(shù)(Local functions)C# 7 中的一個(gè)功能“局部函數(shù)”,如下所示: 1 static IEnumerable<char> GetCharList(string str) 2 { 3 if (IsNullOrWhiteSpace(str)) 4 throw new ArgumentNullException(nameof(str)); 5 6 return GetList(); 7 8 IEnumerable<char> GetList() 9 { 10 for (int i = 0; i < str.Length; i++) 11 { 12 yield return str[i]; 13 } 14 } 15 } 使用方法: 1 [數(shù)據(jù)類(lèi)型,void] 方法名([參數(shù)]) 2 { 3 // Method body,;[] 里面都是可選項(xiàng) 4 } 原理解析:局部函數(shù)雖然是在其他函數(shù)內(nèi)部聲明,,但它編譯后就是一個(gè)被 internal 修飾的靜態(tài)函數(shù),它是屬于類(lèi),,至于它為什么能夠使用上級(jí)函 數(shù)中的局部變量和參數(shù)呢,?那是因?yàn)榫幾g器會(huì)根據(jù)其使用的成員生成一個(gè)新類(lèi)型(Class/Struct)然后將其傳入函數(shù)中。由上可知?jiǎng)t局部函數(shù)的聲 明跟位置無(wú)關(guān),,并可無(wú)限嵌套,。 總結(jié):個(gè)人覺(jué)得局部函數(shù)是對(duì) C# 異常機(jī)制在語(yǔ)義上的一次補(bǔ)充(如上例),以及為代碼提供清晰的結(jié)構(gòu)而設(shè)置的語(yǔ)法,。但局部函數(shù)也有其缺點(diǎn),, 就是局部函數(shù)中的代碼無(wú)法復(fù)用(反射除外)。 6. 更多的表達(dá)式體成員(More expression-bodied members)C# 6 的時(shí)候就支持表達(dá)式體成員,,但當(dāng)時(shí)只支持“函數(shù)成員”和“只讀屬性”,,這一特性在C# 7中得到了擴(kuò)展,,它能支持更多的成員:構(gòu)造函數(shù) ,、析構(gòu)函數(shù),、帶 get,set 訪(fǎng)問(wèn)器的屬性,、以及索引器,。如下所示: 1 public class Student 2 { 3 private string _name; 4 5 // Expression-bodied constructor 6 public Student(string name) => _name = name; 7 8 // Expression-bodied finalizer 9 ~Student() => Console.WriteLine("Finalized!"); 10 11 // Expression-bodied get / set accessors. 12 public string Name 13 { 14 get => _name; 15 set => _name = value ?? "Mike"; 16 } 17 18 // Expression-bodied indexers 19 public string this[string name] => Convert.ToBase64String(Encoding.UTF8.GetBytes(name)); 20 } 備注:索引器其實(shí)在C# 6中就得到了支持,但其它三種在C# 6中未得到支持,。 7. Throw 表達(dá)式(Throw expressions)異常機(jī)制是C#的重要組成部分,,但在以前并不是所有語(yǔ)句都可以?huà)伋霎惓5模纾簵l件表達(dá)式(,? :),、null合并運(yùn)算符(??)、一些Lambda 表達(dá)式,。而使用 C# 7 您可在任意地方拋出異常,。如: 1 public class Student 2 { 3 private string _name = GetName() ?? throw new ArgumentNullException(nameof(GetName)); 4 5 private int _age; 6 7 public int Age 8 { 9 get => _age; 10 set => _age = value <= 0 || value >= 130 ? throw new ArgumentException("參數(shù)不合法") : value; 11 } 12 13 static string GetName() => null; 14 } 8. 擴(kuò)展異步返回類(lèi)型(Generalized async return types)以前異步的返回類(lèi)型必須是:Task、Task<T>,、void,,現(xiàn)在 C# 7 中新增了一種類(lèi)型:ValueTask<T>,如下所示: 1 public async ValueTask<int> Func() 2 { 3 await Task.Delay(3000); 4 return 100; 5 } 總結(jié):ValueTask<T> 與 ValueTuple 非常相似,,所以就不列舉: ValueTask<T> 與 Task 之間的異同了,,但它們都是為了優(yōu)化特定場(chǎng)景性能而 新增的類(lèi)型。 使用 ValueTask<T> 則需要導(dǎo)入: Install - Package System.Threading.Tasks.Extensions 9. 數(shù)字文本語(yǔ)法的改進(jìn)(Numeric literal syntax improvements)C# 7 還包含兩個(gè)新特性:二進(jìn)制文字,、數(shù)字分隔符,,如下所示: 1 var one = 0b0001; 2 var sixteen = 0b0001_0000; 3 4 long salary = 1000_000_000; 5 decimal pi = 3.141_592_653_589m; 注:二進(jìn)制文本是以0b(零b)開(kāi)頭,字母不區(qū)分大小寫(xiě),;數(shù)字分隔符只有三個(gè)地方不能寫(xiě):開(kāi)頭,,結(jié)尾,小數(shù)點(diǎn)前后,。 總結(jié):二進(jìn)制文本,,數(shù)字分隔符 可使常量值更具可讀性。
2-6 處處: http://www.cnblogs.com/zq20/p/6323205.html |
|
來(lái)自: 遠(yuǎn)方 > 《信息技術(shù)》