带你了解C#每个版本新特性
上學時學習C#和.NET,當時網上的資源不像現在這樣豐富,所以去電腦城買了張盜版的VS2005的光盤,安裝時才發現是VS2003,當時有一種被坑的感覺,但也正是如此,讓我有了一個完整的.NET的學習生涯。
一直都認為學習語言應該系統的進行學習,了解每一個版本的新增特性,才能在實際應用中做到有的放矢。最近發現團隊中有不少人雖然用著最新的技術,但知識儲備還停留在一個比較初始的狀態,這樣在編碼過程中會走不少彎路。
本文梳理下C#從1.0到7.0版本的一些常用特性,對于不常用的或者我沒有用到過的一些特性,會列出來,但不會做詳細描述。另外C#8.0現在還沒有正式推出,并且目前我們也只是在使用dotNet Core2.1,所以C#8.0本文也不會涉及。
C#1.X
| 1.0 | VS2002 | 1.0 | 1.0 |
| 1.1 | VS2003 | 1.1 | 1.1 |
在C#1.0或1.1版本中,從語言的角度就是基本的面向對象的語法,可以說任何一本C#語言的書籍都包含了C#1.X的所有內容。
如果您已經在使用C#語言編寫代碼,那么C#1.X的相關知識應該已經掌握。基礎語法部分這里就不再贅述了。
C#2.0
| 2.0 | VS2005 | 2.0 | 2.0 |
2.0中對應VS2005我用的也不多,因為很快就被VS2008替代了,不過在語言方面卻帶來了很多新的東西。
泛型
C#2中最重要的一個特性應該就是泛型。泛型的用處就是在一些場景下可以減少強制轉換來提高性能。在C#1中就有很多的強制轉換,特別是對一些集合進行遍歷時,如ArrayList、HashTable,因為他們是為不同數據類型設計的集合,所以他們中鍵和值的類型都是object,這就意味著會平凡發生裝箱拆箱的操作。C#2中有了泛型,所以我們可以使用List、Dictionary。泛型能夠帶來很好的編譯時類型檢查,也不會有裝箱拆箱的操作,因為類型是在使用泛型的時候就已經指定了。
.NET已經通過了很多的泛型類型供我們使用,如上面提到的List,Dictionary,我們也可以自己來創建泛型類型(類、接口、委托、結構)或是方法。在定義泛型類型或時可以通過定義泛型約束來對泛型參數進行限制,更好的使用編譯時檢查。泛型約束是通過關鍵字where來實現的,C#2中的泛型約束有4種:
分部類(Partil)
分部類可以允許我們在多個文件中為一個類型(class、struct、interface)編寫代碼,在Asp.Net2.0中用的極為廣泛。新建一個Aspx頁面,頁面的CodeBehind和頁面中的控件的定義就是通過分部類來實現的。如下:
public?partial?class?_Default?:?System.Web.UI.Page?public?partial?class?_Default?class?_Default?:?System.Web.UI.Page?public?partial?class?_Default?
分部類使用關鍵字partial來定義,當一個類中的代碼非常多時,可以使用分部類來進行拆分,這對代碼的閱讀很有好處,而且不會影響調用。不過現在我們前后端分離,后端代碼要做到單一職責原則,不會有很多大的類,所以這個特性很少用到。
靜態類
靜態類中的公用方法必須也是靜態的,可以由類名直接調用,不需要實例化,比較適用于編寫一些工具類。如System.Math類就是靜態類。工具類有一些特點,如:所有成員都是靜態的、不需要被繼承、不需要進行實例化。在C#1中我們可以通過如下代碼來實現:
//聲明為密封類防止被繼承?public?sealed?class?StringHelper{????//添加私有無參構造函ˉ數防止被實例化,如果不添加私有構造函數?????//會自動生成共有無參構造函數?????private?StringHelper(){};????public?static?int?StringToInt32(string?input)????{????????int?result=0;????????Int32.TryParse(input,?out?result);????????return?result;????}}public?sealed?class?StringHelper
{
????//添加私有無參構造函ˉ數防止被實例化,如果不添加私有構造函數?
????//會自動生成共有無參構造函數?
????private?StringHelper(){};
????public?static?int?StringToInt32(string?input)
????{
????????int?result=0;
????????Int32.TryParse(input,?out?result);
????????return?result;
????}
}
C#2中可以使用靜態類來實現:
public?static?class?StringHelper{????public?static?int?StringToInt32(string?input)????{????????int?result=0;????????Int32.TryParse(input,?out?result);????????return?result;????}}static?class?StringHelper{
????public?static?int?StringToInt32(string?input)
????{
????????int?result=0;
????????Int32.TryParse(input,?out?result);
????????return?result;
????}
}
屬性的訪問級別
在C#1中聲明屬性,屬性中的get和set的訪問級別是和屬性一致,要么都是public要么都是private,如果要實現get和set有不同的訪問級別,則需要用一種變通的方式,自己寫GetXXX和SetXXX方法。在C#2中可以單獨設置get和set的訪問級別,如下:
private?string?_name;public?string?Name{????get?{?return?_name;?}????private?set?{?_name?=?value;?}}string?_name;public?string?Name
{
????get?{?return?_name;?}
????private?set?{?_name?=?value;?}
}
需要注意的是,不能講屬性設置為私有的,而將其中的get或是set設置成公有的,也不能給set和get設置相同的訪問級別,當set和get的訪問級別相同時,我們可以直接設置在屬性上。
命名空間別名
命名空間可以用來組織類,當不同的命名空間中有相同的類時,可以使用完全限定名來防止類名的沖突,C#1中可以使用空間別名來簡化書寫,空間別名用using關鍵字實現。但還有一些特殊情況,使用using并不能完全解決,所以C#2中提供了下面幾種特性:
我們在構建命名空間和類的時候,盡量避免出現沖突的情況,這個特性也較少用到。
友元程序集
當我們希望一個程序集中的類型可以被外部的某些程序集訪問,這時如果設置成Public,就可以被所有的外部程序集訪問。怎樣只讓部分程序集訪問,就要使用友元程序集了,具體參考之前的博文《C#:友元程序集(http://blog.fwhyy.com/2010/11/csharp-a-friend-assembly/)》
可空類型
可空類型就是允許值類型的值為null。通常值類型的值是不應該為null的,但我們很多應用是和數據庫打交道的,而數據庫中的類型都是可以為null值的,這就造成了我們寫程序的時候有時需要將值類型設置為null。在C#1中通常使用”魔值“來處理這種情況,比如DateTiem.MinValue、Int32.MinValue。在ADO.NET中所有類型的空值可以用DBNull.Value來表示。C#2中可空類型主要是使用System.Nullable的泛型類型,類型參數T有值類型約束。可以像下面這樣來定義可空類型:
Nullable<int>?i?=?20;Nullable<bool>?b?=?true;20;Nullable<bool>?b?=?true;
C#2中也提供了更方便的定義方式,使用操作符?:
int??i?=?20;bool??b?=?true;20;bool??b?=?true;
迭代器
C#2中對迭代器提供了更便捷的實現方式。提到迭代器,有兩個概念需要了解
看下面一個例子:
public?class?Test?{????static?void?Main()????{????????Person?arrPerson?=?new?Person("oec2003","oec2004","oec2005");????????foreach?(string?p?in?arrPerson)????????{????????????Console.WriteLine(p);????????}????????Console.ReadLine();????}}public?class?Person:IEnumerable?{????public?Person(params?string[]?names)????{????????_names?=?new?string[names.Length];????????names.CopyTo(_names,?0);????}????public?string[]?_names;????public?IEnumerator?GetEnumerator()????{????????return?new?PersonEnumerator(this);????}????private?string?this[int?index]????{????????get?{?return?_names[index];?}????????set?{?_names[index]?=?value;?}????}}public?class?PersonEnumerator?:?IEnumerator?{????private?int?_index?=?-1;????private?Person?_p;????public?PersonEnumerator(Person?p)?{?_p?=?p;?}????public?object?Current????{????????get?{?return?_p._names[_index];?}????}????public?bool?MoveNext()????{????????_index++;????????return?_index?<?_p._names.Length;????}????public?void?Reset()????{????????_index?=?-1;????}}class?Test?{
????static?void?Main()
????{
????????Person?arrPerson?=?new?Person("oec2003","oec2004","oec2005");
????????foreach?(string?p?in?arrPerson)
????????{
????????????Console.WriteLine(p);
????????}
????????Console.ReadLine();
????}
}
public?class?Person:IEnumerable?
{
????public?Person(params?string[]?names)
????{
????????_names?=?new?string[names.Length];
????????names.CopyTo(_names,?0);
????}
????public?string[]?_names;
????public?IEnumerator?GetEnumerator()
????{
????????return?new?PersonEnumerator(this);
????}
????private?string?this[int?index]
????{
????????get?{?return?_names[index];?}
????????set?{?_names[index]?=?value;?}
????}
}
public?class?PersonEnumerator?:?IEnumerator?
{
????private?int?_index?=?-1;
????private?Person?_p;
????public?PersonEnumerator(Person?p)?{?_p?=?p;?}
????public?object?Current
????{
????????get?{?return?_p._names[_index];?}
????}
????public?bool?MoveNext()
????{
????????_index++;
????????return?_index?<?_p._names.Length;
????}
????public?void?Reset()
????{
????????_index?=?-1;
????}
}
C#2中的迭代器變得非常便捷,使用關鍵字yield return關鍵字實現,下面是C#2中使用yield return的重寫版本:
public?class?Test?{????static?void?Main()????{????????Person?arrPerson?=?new?Person("oec2003","oec2004","oec2005");????????foreach?(string?p?in?arrPerson)????????{????????????Console.WriteLine(p);????????}????????Console.ReadLine();????}}public?class?Person:IEnumerable?{????public?Person(params?string[]?names)????{????????_names?=?new?string[names.Length];????????names.CopyTo(_names,?0);????}????public?string[]?_names;????public?IEnumerator?GetEnumerator()????{????????foreach?(string?s?in?_names)????????{????????????yield?return?s;????????}????}}class?Test?{
????static?void?Main()
????{
????????Person?arrPerson?=?new?Person("oec2003","oec2004","oec2005");
????????foreach?(string?p?in?arrPerson)
????????{
????????????Console.WriteLine(p);
????????}
????????Console.ReadLine();
????}
}
public?class?Person:IEnumerable?
{
????public?Person(params?string[]?names)
????{
????????_names?=?new?string[names.Length];
????????names.CopyTo(_names,?0);
????}
????public?string[]?_names;
????public?IEnumerator?GetEnumerator()
????{
????????foreach?(string?s?in?_names)
????????{
????????????yield?return?s;
????????}
????}
}
匿名方法
匿名方法比較適用于定義必須通過委托調用的方法,用多線程來舉個例子,在C#1中代碼如下:
private?void?btnTest_Click(object?sender,?EventArgs?e){????Thread?thread?=?new?Thread(new?ThreadStart(DoWork));????thread.Start();}private?void?DoWork(){????for?(int?i?=?0;?i?<?100;?i++)????{????????Thread.Sleep(100);????????this.Invoke(new?Action<string>(this.ChangeLabel),i.ToString());????}}private?void?ChangeLabel(string?i){????label1.Text?=?i?+?"/100";}????Thread?thread?=?new?Thread(new?ThreadStart(DoWork));
????thread.Start();
}
private?void?DoWork()
{
????for?(int?i?=?0;?i?<?100;?i++)
????{
????????Thread.Sleep(100);
????????this.Invoke(new?Action<string>(this.ChangeLabel),i.ToString());
????}
}
private?void?ChangeLabel(string?i)
{
????label1.Text?=?i?+?"/100";
}
使用C#2中的匿名方法,上面的例子中可以省去DoWork和ChangeLabel兩個方法,代碼如下:
private?void?btnTest_Click(object?sender,?EventArgs?e){????Thread?thread?=?new?Thread(new?ThreadStart(delegate()?{????????for?(int?i?=?0;?i?<?100;?i++)????????{????????????Thread.Sleep(100);????????????this.Invoke(new?Action(delegate()?{?label1.Text?=?i?+?"/100";?}));????????}????}));????thread.Start();}????Thread?thread?=?new?Thread(new?ThreadStart(delegate()?{
????????for?(int?i?=?0;?i?<?100;?i++)
????????{
????????????Thread.Sleep(100);
????????????this.Invoke(new?Action(delegate()?{?label1.Text?=?i?+?"/100";?}));
????????}
????}));
????thread.Start();
}
其他相關特性
C#3.0
| 3.0 | VS2008 | 2.0 | 3.0 3.5 |
如果說C#2中的核心是泛型的話,那么C#3中的核心就應是Linq了,C#3中的特性幾乎都是為Linq服務的,但每一項特性都可以脫離Linq來使用。下面就來看下C#3中有哪些特性。
自動實現的屬性
這個特性非常簡單,就是使定義屬性變得更簡單了。代碼如下:
public?string?Name?{?get;?set;?}public?int?Age?{?private?set;?get;?}string?Name?{?get;?set;?}public?int?Age?{?private?set;?get;?}
隱式類型的局部變量和擴展方法
隱式類型的局部變量是讓我們在定義變量時可以比較動態化,使用var關鍵字作為類型的占位符,然后由編譯器來推導變量的類型。
擴展方法可以在現有的類型上添加一些自定義的方法,比如可以在string類型上添加一個擴展方法ToInt32,就可以像“20”.ToInt32()這樣調用了。
具體參見《C#3.0學習(1)—隱含類型局部變量和擴展方法(http://blog.fwhyy.com/2008/02/learning-csharp-3-0-1-implied-type-of-local-variables-and-extension-methods/)》。
隱式類型雖然讓編碼方便了,但有些不少限制:
對象集合初始化器
簡化了對象和集合的創建,具體參見《C#3.0學習(2)—對象集合初始化器(http://blog.fwhyy.com/2008/02/learning-c-3-0-2-object-collection-initializer/)》。
隱式類型的數組
和隱式類型的局部變量類似,可以不用顯示指定類型來進行數組的定義,通常我們定義數組是這樣:
string[]?names?=?{?"oec2003",?"oec2004",?"oec2005"?};"oec2003",?"oec2004",?"oec2005"?};使用匿名類型數組可以想下面這樣定義:
protected?void?Page_Load(object?sender,?EventArgs?e){????GetName(new[]?{?"oec2003",?"oec2004",?"oec2005"?});}public?string?GetName(string[]?names){????return?names[0];}????GetName(new[]?{?"oec2003",?"oec2004",?"oec2005"?});
}
public?string?GetName(string[]?names)
{
????return?names[0];
}
匿名類型
匿名類型是在初始化的時候根據初始化列表自動產生類型的一種機制,利用對象初始化器來創建匿名對象的對象,具體參見《C#3.0學習(3)—匿名類型(http://blog.fwhyy.com/2008/03/learning-csharp-3-0-3-anonymous-types/)》。
Lambda表達式
實際上是一個匿名方法,Lambda表達的表現形式是:(參數列表)=>{語句},看一個例子,創建一個委托實例,獲取一個string類型的字符串,并返回字符串的長度。代碼如下:
Func<string,?int>?func?=?delegate(string?s)?{?return?s.Length;?};Console.WriteLine(func("oec2003"));int>?func?=?delegate(string?s)?{?return?s.Length;?};Console.WriteLine(func("oec2003"));
使用Lambda的寫法如下:
Func<string,?int>?func?=?(string?s)=>?{?return?s.Length;?};Func<string,?int>?func1?=?(s)?=>?{?return?s.Length;?};Func<string,?int>?func2?=?s?=>?s.Length;int>?func?=?(string?s)=>?{?return?s.Length;?};Func<string,?int>?func1?=?(s)?=>?{?return?s.Length;?};
Func<string,?int>?func2?=?s?=>?s.Length;
上面三種寫法是逐步簡化的過程。
Lambda表達式樹
是.NET3.5中提出的一種表達方式,提供一種抽象的方式將一些代碼表示成一個對象樹。要使用Lambda表達式樹需要引用命名空間System.Linq.Expressions,下面代碼構建一個1+2的表達式樹,最終表達式樹編譯成委托來得到執行結果:
Expression?a?=?Expression.Constant(1);Expression?b?=?Expression.Constant(2);Expression?add?=?Expression.Add(a,?b);Console.WriteLine(add);?//(1+2)?Func<int>?fAdd?=?Expression.Lambda<Func<int>>(add).Compile();Console.WriteLine(fAdd());?//3?Expression?b?=?Expression.Constant(2);
Expression?add?=?Expression.Add(a,?b);
Console.WriteLine(add);?//(1+2)?Func<int>?fAdd?=?Expression.Lambda<Func<int>>(add).Compile();
Console.WriteLine(fAdd());?//3?
Lambda和Lambda表達式樹為我們使用Linq提供了很多支持,如果我們在做的一個管理系統使用了Linq To Sql,在列表頁會有按多個條件來進行數據的篩選的功能,這時就可以使用Lambda表達式樹來進行封裝查詢條件,下面的類封裝了And和Or兩種條件:
public?static?class?DynamicLinqExpressions?{????public?static?Expression<Func<T,?bool>>?True<T>()?{?return?f?=>?true;?}????public?static?Expression<Func<T,?bool>>?False<T>()?{?return?f?=>?false;?}????public?static?Expression<Func<T,?bool>>?Or<T>(this?Expression<Func<T,?bool>>?expr1,????????????????????????????????????????????????????????Expression<Func<T,?bool>>?expr2)????{????????var?invokedExpr?=?Expression.Invoke(expr2,?expr1.Parameters.Cast<Expression>());????????return?Expression.Lambda<Func<T,?bool>>??????????????(Expression.Or(expr1.Body,?invokedExpr),?expr1.Parameters);????}????public?static?Expression<Func<T,?bool>>?And<T>(this?Expression<Func<T,?bool>>?expr1,?????????????????????????????????????????????????????????Expression<Func<T,?bool>>?expr2)????{????????var?invokedExpr?=?Expression.Invoke(expr2,?expr1.Parameters.Cast<Expression>());????????return?Expression.Lambda<Func<T,?bool>>??????????????(Expression.And(expr1.Body,?invokedExpr),?expr1.Parameters);????}}static?class?DynamicLinqExpressions?{
????public?static?Expression<Func<T,?bool>>?True<T>()?{?return?f?=>?true;?}
????public?static?Expression<Func<T,?bool>>?False<T>()?{?return?f?=>?false;?}
????public?static?Expression<Func<T,?bool>>?Or<T>(this?Expression<Func<T,?bool>>?expr1,
????????????????????????????????????????????????????????Expression<Func<T,?bool>>?expr2)
????{
????????var?invokedExpr?=?Expression.Invoke(expr2,?expr1.Parameters.Cast<Expression>());
????????return?Expression.Lambda<Func<T,?bool>>
??????????????(Expression.Or(expr1.Body,?invokedExpr),?expr1.Parameters);
????}
????public?static?Expression<Func<T,?bool>>?And<T>(this?Expression<Func<T,?bool>>?expr1,
?????????????????????????????????????????????????????????Expression<Func<T,?bool>>?expr2)
????{
????????var?invokedExpr?=?Expression.Invoke(expr2,?expr1.Parameters.Cast<Expression>());
????????return?Expression.Lambda<Func<T,?bool>>
??????????????(Expression.And(expr1.Body,?invokedExpr),?expr1.Parameters);
????}
}
下面是獲取條件的方法:
public?Expression<Func<Courses,?bool>>?GetCondition(){????var?exp?=?DynamicLinqExpressions.True<Courses>();????if?(txtCourseName.Text.Trim().Length?>?0)????{????????exp?=?exp.And(g?=>?g.CourseName.Contains(txtCourseName.Text.Trim()));????}????if?(ddlGrade.SelectedValue?!=?"-1")????{????????exp=exp.And(g?=>?g.GradeID.Equals(ddlGrade.SelectedValue));????}????return?exp;}{
????var?exp?=?DynamicLinqExpressions.True<Courses>();
????if?(txtCourseName.Text.Trim().Length?>?0)
????{
????????exp?=?exp.And(g?=>?g.CourseName.Contains(txtCourseName.Text.Trim()));
????}
????if?(ddlGrade.SelectedValue?!=?"-1")
????{
????????exp=exp.And(g?=>?g.GradeID.Equals(ddlGrade.SelectedValue));
????}
????return?exp;
}
Linq
Linq是一個很大的話題,也是NET3.5中比較核心的內容,有很多書籍專門來介紹Linq,下面只是做一些簡單的介紹,需要注意的是Linq并非是Linq To Sql,Linq是一個大的集合,里面包含:
下面以Linq To Object為例子來看看Linq是怎么使用的:
public?class?UserInfo?{????public?string?Name?{?get;?set;?}????public?int?Age?{?get;?set;?}}public?class?Test?{????static?void?Main()????{????????List<UserInfo>?users?=?new?List<UserInfo>()????????{????????????new?UserInfo{Name="oec2003",Age=20},????????????new?UserInfo{Name="oec2004",Age=21},????????????new?UserInfo{Name="oec2005",Age=22}????????};????????IEnumerable<UserInfo>?selectedUser?=?from?user?in?users?????????????????????????????????????????????where?user.Age?>?20?????????????????????????????????????????????orderby?user.Age?descending?select?user;????????foreach?(UserInfo?user?in?selectedUser)????????{????????????Console.WriteLine("姓名:"+user.Name+",年齡:"+user.Age);????????}????????Console.ReadLine();????}}class?UserInfo?{
????public?string?Name?{?get;?set;?}
????public?int?Age?{?get;?set;?}
}
public?class?Test?
{
????static?void?Main()
????{
????????List<UserInfo>?users?=?new?List<UserInfo>()
????????{
????????????new?UserInfo{Name="oec2003",Age=20},
????????????new?UserInfo{Name="oec2004",Age=21},
????????????new?UserInfo{Name="oec2005",Age=22}
????????};
????????IEnumerable<UserInfo>?selectedUser?=?from?user?in?users
?????????????????????????????????????????????where?user.Age?>?20
?????????????????????????????????????????????orderby?user.Age?descending?select?user;
????????foreach?(UserInfo?user?in?selectedUser)
????????{
????????????Console.WriteLine("姓名:"+user.Name+",年齡:"+user.Age);
????????}
????????Console.ReadLine();
????}
}
可以看出,Linq可以讓我們使用類似Sql的關鍵字來對集合、對象、XML等進行查詢。
C#4.0
| 4.0 | VS2010 | 4.0 | 4.0 |
可選參數
VB在很早就已經支持了可選參數,而C#知道4了才支持,顧名思義,可選參數就是一些參數可以是可選的,在方法調用的時候可以不用輸入。看下面代碼:
public?class?Test?{????static?void?Main()????{????????Console.WriteLine(GetUserInfo());?//姓名:ooec2003,年齡:30?????????Console.WriteLine(GetUserInfo("oec2004",?20));//姓名:ooec2004,年齡:20?????????Console.ReadLine();????}????public?static?string?GetUserInfo(string?name?=?"oec2003",?int?age?=?30)????{????????return?"姓名:"?+?name?+?",年齡:"?+?age.ToString();????}}class?Test?{
????static?void?Main()
????{
????????Console.WriteLine(GetUserInfo());?//姓名:ooec2003,年齡:30?
????????Console.WriteLine(GetUserInfo("oec2004",?20));//姓名:ooec2004,年齡:20?
????????Console.ReadLine();
????}
????public?static?string?GetUserInfo(string?name?=?"oec2003",?int?age?=?30)
????{
????????return?"姓名:"?+?name?+?",年齡:"?+?age.ToString();
????}
}
命名實參
命名實參是在制定實參的值時,可以同時指定相應參數的名稱。編譯器可以判斷參數的名稱是否正確,命名實參可以讓我們在調用時改變參數的順序。命名實參也經常和可選參數一起使用,看下面的代碼:
static?void?Main(){????Console.WriteLine(Cal());//9?????Console.WriteLine(Cal(z:?5,?y:?4));//25?????Console.ReadLine();}public?static?int?Cal(int?x=1,?int?y=2,?int?z=3){????return?(x?+?y)?*?z;}????Console.WriteLine(Cal());//9?
????Console.WriteLine(Cal(z:?5,?y:?4));//25?
????Console.ReadLine();
}
public?static?int?Cal(int?x=1,?int?y=2,?int?z=3)
{
????return?(x?+?y)?*?z;
}
通過可選參數和命名參數的結合使用,我們可以減少代碼中方法的重載。
動態類型
C#使用dynamic來實現動態類型,在沒用使用dynamic的地方,C#依然是靜態的。靜態類型中當我們要使用程序集中的類,要調用類中的方法,編譯器必須知道程序集中有這個類,類里有這個方法,如果不能事先知道,編譯時會報錯,在C#4以前可以通過反射來解決這個問題。看一個使用dynamic的小例子:
dynamic?a?=?"oec2003";Console.WriteLine(a.Length);//7?Console.WriteLine(a.length);//string?類型不包含length屬性,但編譯不會報錯,運行時會報錯?Console.ReadLine();"oec2003";Console.WriteLine(a.Length);//7?
Console.WriteLine(a.length);//string?類型不包含length屬性,但編譯不會報錯,運行時會報錯?
Console.ReadLine();
您可能會發現使用dynamic聲明變量和C#3中提供的var有點類似,其他他們是有本質區別的,var聲明的變量在編譯時會去推斷出實際的類型,var只是相當于一個占位符,而dynamic聲明的變量在編譯時不會進行類型檢查。
dynamic用的比較多的應該是替代以前的反射,而且性能有很大提高。假設有一個名為DynamicLib的程序集中有一個DynamicClassDemo類,類中有一個Cal方法,下面看看利用反射怎么訪問Cal方法:
namespace?DynamicLib{????public?class?DynamicClassDemo?????{????????public?int?Cal(int?x?=?1,?int?y?=?2,?int?z?=?3)????????{????????????return?(x?+?y)?*?z;????????}????}}static?void?Main(){????Assembly?assembly?=?Assembly.Load("DynamicLib");????object?obj?=?assembly.CreateInstance("DynamicLib.DynamicClassDemo");????Type?type?=?obj.GetType();????MethodInfo?method?=?type.GetMethod("Cal");????Console.WriteLine(method.Invoke(obj,?new?object[]?{?1,?2,?3?}));//9?????Console.ReadLine();}DynamicLib{
????public?class?DynamicClassDemo?
????{
????????public?int?Cal(int?x?=?1,?int?y?=?2,?int?z?=?3)
????????{
????????????return?(x?+?y)?*?z;
????????}
????}
}
static?void?Main()
{
????Assembly?assembly?=?Assembly.Load("DynamicLib");
????object?obj?=?assembly.CreateInstance("DynamicLib.DynamicClassDemo");
????Type?type?=?obj.GetType();
????MethodInfo?method?=?type.GetMethod("Cal");
????Console.WriteLine(method.Invoke(obj,?new?object[]?{?1,?2,?3?}));//9?
????Console.ReadLine();
}
用dynamic的代碼如下:
Assembly?assembly?=?Assembly.Load("DynamicLib");dynamic?obj?=?assembly.CreateInstance("DynamicLib.DynamicClassDemo");Console.WriteLine(obj.Cal());Console.ReadLine();assembly?=?Assembly.Load("DynamicLib");dynamic?obj?=?assembly.CreateInstance("DynamicLib.DynamicClassDemo");
Console.WriteLine(obj.Cal());
Console.ReadLine();
在前后端分離的模式下,WebAPI接口的參數也可以采用dynamic來定義,直接就可以解析前端傳入的json參數,不用每一個接口方法都定義一個參數類型。不好的地方就是通過Swagger來生產API文檔時,不能明確的知道輸入參數的每個屬性的含義。
C#4中還有一些COM互操作性的改進和逆變性和協變性的改進,我幾乎沒有用到,所以在此就不講述了。
C#5.0
| 5.0 | VS2012\2013 | 4.0 | 4.5 |
異步處理
異步處理是C#5中很重要的一個特性,會涉及到兩個關鍵字:async和await,要講明白這個需要單獨寫一篇來介紹。
可以簡單理解為,當Winform窗體程序中有一個耗時操作時,如果是同步操作,窗體在返回結果之前會卡死,當然在C#5之前的版本中有多種方法可以來解決這個問題,但C#5的異步處理解決的更優雅。
循環中捕獲變量
與其說是一個特性,不如說是對之前版本問題的修復,看下面的代碼:
public?static?void?CapturingVariables(){????string[]?names?=?{?"oec2003","oec2004","oec2005"};????var?actions?=?new?List<Action>();????foreach(var?name?in?names)????{????????actions.Add(()?=>?Console.WriteLine(name));????}????foreach(Action?action?in?actions)????{????????action();????}}????string[]?names?=?{?"oec2003","oec2004","oec2005"};
????var?actions?=?new?List<Action>();
????foreach(var?name?in?names)
????{
????????actions.Add(()?=>?Console.WriteLine(name));
????}
????foreach(Action?action?in?actions)
????{
????????action();
????}
}
這段代碼在之前的C#版本中,會連續輸出三個oec2005,在C#5中會按照我們的期望依次輸出oec2003、oec2004、oec2005。
如果您的代碼在之前的版本中有利用到這個錯誤的結果,那么在升級到C#5或以上版本中就要注意了。
調用者信息特性
我們的程序通常是以release形式發布,發布后很難追蹤到代碼執行的具體信息,在C#5中提供了三種特性(Attribute), 允許獲取調用者的當前編譯器的執行文件名、所在行數與方法或屬性名稱。代碼如下:
static?void?Main(string[]?args){????ShowInfo();????Console.ReadLine();}public?static?void?ShowInfo(???[CallerFilePath]?string?file?=?null,???[CallerLineNumber]?int?number?=?0,???[CallerMemberName]?string?name?=?null){????Console.WriteLine($"filepath:{file}");????Console.WriteLine($"rownumber:{number}");????Console.WriteLine($"methodname:{name}");}????ShowInfo();
????Console.ReadLine();
}
public?static?void?ShowInfo(
???[CallerFilePath]?string?file?=?null,
???[CallerLineNumber]?int?number?=?0,
???[CallerMemberName]?string?name?=?null)
{
????Console.WriteLine($"filepath:{file}");
????Console.WriteLine($"rownumber:{number}");
????Console.WriteLine($"methodname:{name}");
}
調用結果如下:
filepath:/Users/ican_macbookpro/Projects/CsharpFeature/CsharpFeature5/Program.csrownumber:12methodname:Mainrownumber:12
methodname:Main
C#6.0
| 6.0 | VS2015 | 4.0 | 4.6 |
在C#6中提供了不少的新功能,我認為最有用的就是Null條件運算符和字符串嵌入。
Null條件運算符
在C#中,一個常見的異常就是“未將對象引用到對象的實例”,原因是對引用對象沒有做非空判斷導致。在團隊中雖然再三強調,但依然會在這個問題上栽跟頭。下面的代碼就會導致這個錯誤:
class?Program{????static?void?Main(string[]?args)????{????????//Null條件運算符????????User?user?=?null;????????Console.WriteLine(user.GetUserName());????????Console.ReadLine();????}}class?User{????public?string?GetUserName()?=>?"oec2003";}Program{
????static?void?Main(string[]?args)
????{
????????//Null條件運算符
????????User?user?=?null;
????????Console.WriteLine(user.GetUserName());
????????Console.ReadLine();
????}
}
class?User
{
????public?string?GetUserName()?=>?"oec2003";
}
要想不出錯,就需要對user對象做非空判斷
if(user!=null){????Console.WriteLine(user.GetUserName());?}null){
????Console.WriteLine(user.GetUserName());?
}
在C#6中可以用很簡單的方式來處理這個問題
//Null條件運算符User?user?=?null;Console.WriteLine(user?.GetUserName());?User?user?=?null;
Console.WriteLine(user?.GetUserName());?
注:雖然這個語法糖非常簡單,也很好用,但在使用時也需要多想一步,當對象為空時,調用其方法返回的值也是空,這樣的值對后續的操作會不會有影響,如果有,還是需要做判斷,并做相關的處理。
字符串嵌入
字符串嵌入可以簡化字符串的拼接,很直觀的就可以知道需要表達的意思,在C#6及以上版本中都應該用這種方式來處理字符串拼接,代碼如下:
//字符串嵌入string?name?=?"oec2003";//之前版本的處理方式1Console.WriteLine("Hello?"?+?name);//之前版本的處理方式2Console.WriteLine(string.Format("Hello?{0}",name));//C#6字符串嵌入的處理方式Console.WriteLine($"Hello?{name}");string?name?=?"oec2003";
//之前版本的處理方式1
Console.WriteLine("Hello?"?+?name);
//之前版本的處理方式2
Console.WriteLine(string.Format("Hello?{0}",name));
//C#6字符串嵌入的處理方式
Console.WriteLine($"Hello?{name}");
其他相關特性
C#7.0
| 7.0 | VS2017 15.0 | .NET Core1.0 |
| 7.1 | VS2017 15.3 | .NET Core2.0 |
| 7.2 | VS2017 15.5 | .NET Core2.0 |
| 7.3 | VS2017 15.7 | .NET Core2.1 |
out 變量
此特性簡化了out變量的使用,之前的版本中使用代碼如下:
int?result?=?0;int.TryParse("20",?out?result);Console.WriteLine(result);0;int.TryParse("20",?out?result);
Console.WriteLine(result);
優化后的代碼,不需要事先定義一個變量
int.TryParse("20",?out?var?result);Console.WriteLine(result);"20",?out?var?result);Console.WriteLine(result);
模式匹配
這也是一個減少我們編碼的語法糖,直接看代碼吧
public?class?PatternMatching{????public?void?Test()????{????????List<Person>?list?=?new?List<Person>();????????list.Add(new?Man());????????list.Add(new?Woman());????????foreach?(var?item?in?list)????????{?????????????????//在之前版本中此處需要做類型判斷和類型轉換????????????if?(item?is?Man?man)????????????????Console.WriteLine(man.GetName());????????????else?if?(item?is?Woman?woman)????????????????Console.WriteLine(woman.GetName());????????}????}}public?abstract?class?Person{????public?abstract?string?GetName();}public?class?Man:Person{????public?override?string?GetName()?=>?"Man";}public?class?Woman?:?Person{????public?override?string?GetName()?=>?"Woman";}class?PatternMatching{
????public?void?Test()
????{
????????List<Person>?list?=?new?List<Person>();
????????list.Add(new?Man());
????????list.Add(new?Woman());
????????foreach?(var?item?in?list)
????????{
?????????????????//在之前版本中此處需要做類型判斷和類型轉換
????????????if?(item?is?Man?man)
????????????????Console.WriteLine(man.GetName());
????????????else?if?(item?is?Woman?woman)
????????????????Console.WriteLine(woman.GetName());
????????}
????}
}
public?abstract?class?Person
{
????public?abstract?string?GetName();
}
public?class?Man:Person
{
????public?override?string?GetName()?=>?"Man";
}
public?class?Woman?:?Person
{
????public?override?string?GetName()?=>?"Woman";
}
詳細參考官方文檔:https://docs.microsoft.com/zh-cn/dotnet/csharp/pattern-matching
本地方法
可以在方法中寫內部方法,在方法中有時需要在多個代碼邏輯執行相同的處理,之前的做法是在類中寫私有方法,現在可以讓這個私有方法寫在方法的內部,提高代碼可讀性。
static?void?LocalMethod(){????string?name?=?"oec2003";????string?name1?=?"oec2004";????Console.WriteLine(AddPrefix(name));????Console.WriteLine(AddPrefix(name1));????string?AddPrefix(string?n)????{????????return?$"Hello?{n}";????}}????string?name?=?"oec2003";
????string?name1?=?"oec2004";
????Console.WriteLine(AddPrefix(name));
????Console.WriteLine(AddPrefix(name1));
????string?AddPrefix(string?n)
????{
????????return?$"Hello?{n}";
????}
}
異步 main 方法
這個最大的好處是,在控制臺程序中調試異步方法變得很方便。
static?async?Task?Main(){????await?SomeAsyncMethod();}????await?SomeAsyncMethod();
}
private protected 訪問修飾符
可以限制在同一個程序集中的派生類的訪問,是對protected internal的一種補強,protected internal是指同一程序集中的類或派生類進行訪問。
其他相關特性
總結
每個特性都需要我們去編碼實現下,了解了真正的含義和用途,我們才能在工作中靈活的運用。
本文所涉及到的實例代碼后面也會上傳到Github上。
希望本文對您有所幫助。
總結
以上是生活随笔為你收集整理的带你了解C#每个版本新特性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 不装 VS 自己编译安装 Windows
- 下一篇: 从零开始制作 NuGet 源代码包(全面