C#各版本新特性
C#各版本新特性
C# 2.0
泛型(Generics)
泛型是CLR 2.0中引入的最重要的新特性,使得可以在類、方法中對使用的型別進行引數化。
例如,這裡定義了一個泛型類:
class MyCollection<T> { T variable1; private void Add(T param) { } }
使用的時候:
MyCollection<string> list2 = new MyCollection<string>();
MyCollection<Object> list3 = new MyCollection<Object>();
泛型的好處
- 編譯時就可以保證型別安全
- 不用做型別裝換,獲得一定的效能提升
泛型方法、泛型委託、泛型介面
除了泛型類之外,還有泛型方法、泛型委託、泛型介面:
//泛型委託 public static delegate T1 MyDelegate<T1, T2>(T2 item);
MyDelegate<Int32, String> MyFunc = new MyDelegate<Int32, String>(SomeMethd); //泛型介面 public class MyClass<T1, T2, T3> : MyInteface<T1, T2, T3> { public T1 Method1(T2 param1, T3 param2) { throw new NotImplementedException(); } } interface MyInteface<T1, T2, T3> { T1 Method1(T2 param1, T3 param2); } //泛型方法 static void Swap<T>(ref T t1, ref T t2) { T temp = t1; t1 = t2; t2 = temp; }String str1 = "a"; String str2 = "b"; Swap<String>(ref str1, ref str2);
泛型約束(constraints)
可以給泛型的型別引數上加約束,可以要求這些型別引數滿足一定的條件
約束 |
說明 |
where T: struct | 型別引數需是值型別 |
where T : class | 型別引數需是引用型別 |
where T : new() | 型別引數要有一個public的無參建構函式 |
where T : <base class name> | 型別引數要派生自某個基類 |
where T : <interface name> | 型別引數要實現了某個介面 |
where T : U | 這裡T和U都是型別引數,T必須是或者派生自U |
這些約束,可以同時一起使用:
class EmployeeList<T> where T : Employee, IEmployee, System.IComparable<T>, new()
{
// ...
}
default 關鍵字
這個關鍵可以使用在型別引數上:
default(T);
對於值型別,返回0,引用型別,返回null,對於結構型別,會返回一個成員值全部為0的結構例項。
迭代器(iterator)
可以在不實現IEnumerable就能使用foreach語句,在編譯器碰到yield return時,它會自動生成IEnumerable 介面的方法。在實現迭代器的方法或屬性中,返回型別必須是IEnumerable, IEnumerator, IEnumerable<T>,或 IEnumerator<T>。迭代器使得遍歷一些零碎資料的時候很方便,不用去實現Current, MoveNext 這些方法。
publicSystem.Collections.IEnumerator GetEnumerator() {yieldreturn-1;for(inti = 1; i < max; i++) {yieldreturni; } }
可空型別(Nullable Type)
可空型別System.Nullable<T>,可空型別僅針對於值型別,不能針對引用型別去建立。System.Nullable<T>簡寫為T ?。
int? num =null;if(num.HasValue ==true) { System.Console.WriteLine("num = "+ num.Value); }else{ System.Console.WriteLine("num = Null"); }
如果HasValue為false,那麼在使用value值的時候會丟擲異常。把一個Nullable的變數x賦值給一個非Nullable的變數y可以這麼寫:
inty = x ?? -1;
匿名方法(Anonymous Method)
在C#2.0之前,給只能用一個已經申明好的方法去建立一個委託。有了匿名方法後,可以在建立委託的時候直接傳一個程式碼塊過去。
delegatevoidDel(intx); Del d =delegate(intk) {/* ... */}; System.Threading.Thread t1 =newSystem.Threading.Thread (delegate() { System.Console.Write("Hello, "); } );委託語法的簡化// C# 1.0的寫法ThreadStart ts1 =newThreadStart(Method1);// C# 2.0可以這麼寫ThreadStart ts2 = Method1;
委託的協變和逆變(covariance and contravariance)
有下面的兩個類:
classParent { }classChild: Parent { }
然後看下面的兩個委託:
publicdelegateParent DelgParent();
publicdelegateChild DelgChild();
publicstaticParent Method1() {returnnull; }
publicstaticChild Method2() {returnnull; }
staticvoidMain() { DelgParent del1= Method1; DelgChild del2= Method2; del1 = del2; }
注意上面的,DelgParent 和DelgChild 是完全不同的型別,他們之間本身沒有任何的繼承關係,所以理論上來說他們是不能相互賦值的。但是因為協變的關係,使得我們可以把DelgChild型別的委託賦值給DelgParent 型別的委託。協變針對委託的返回值,逆變針對引數,原理是一樣的。
部分類(partial)
在申明一個類、結構或者介面的時候,用partial關鍵字,可以讓原始碼分佈在不同的檔案中。我覺得這個東西完全是為了照顧Asp.net程式碼分離而引入的功能,真沒什麼太大的實際用處。微軟說在一些大工程中可以把類分開在不同的檔案中讓不同的人去實現,方便團隊協作,這個我覺得純屬胡扯。
部分類僅是編譯器提供的功能,在編譯的時候會把partial關鍵字定義的類和在一起去編譯,和CRL沒什麼關係。
靜態類(static class)
靜態類就一個只能有靜態成員的類,用static關鍵字對類進行標示,靜態類不能被例項化。靜態類理論上相當於一個只有靜態成員並且建構函式為私有的普通類,靜態類相對來說的好處就是,編譯器能夠保證靜態類不會新增任何非靜態成員。
global::
這個代表了全域性名稱空間(最上層的名稱空間),也就是任何一個程式的預設名稱空間。
classTestApp {publicclassSystem { }constintConsole = 7;staticvoidMain() {//用這個訪問就會出錯,System和Console都被佔用了//Console.WriteLine(number);global::System.Console.WriteLine(number); } }
extern alias
用來消除不同程式集中類名重複的衝突,這樣可以引用同一個程式集的不同版本,也就是說在編譯的時候,提供了一個將有衝突的程式集進行區分的手段。
在編譯的時候,使用命令列引數來指明alias,例如:
/r:aliasName=assembly1.dll
在Visual Studio裡面,在被引用的程式集的屬性裡面可以指定Alias的值,預設是global。
然後在程式碼裡面就可以使用了:
externalias aliasName;//這行需要在using這些語句的前面usingSystem;usingSystem.Collections.Generic;usingSystem.Text;usingaliasName.XXX;
屬性Accessor訪問控制
publicvirtualintTestProperty {protectedset { } get {return0; } }
友元程式集(Friend Assembly)
可以讓其它程式集訪問自己的internal成員(private的還是不行),使用Attributes來實現,例如:
[assembly:InternalsVisibleTo("cs_friend_assemblies_2")]
注意這個作用範圍是整個程式集。
fixed關鍵字
可以使用fixed關鍵字來建立固定長度的陣列,但是陣列只能是bool, byte, char, short, int, long, sbyte, ushort, uint, ulong, float, double中的一種。
這主要是為了更好的處理一些非託管的程式碼。比如下面的這個結構體:
publicstructMyArray {publicfixedcharpathName[128]; }
如果不用fixed的話,無法預先佔住128個char的空間,使用fixed後可以很好的和非託管程式碼進行互動。
volatile關鍵字
用來表示相關的字可能被多個執行緒同時訪問,編譯器不會對相應的值做針對單執行緒下的優化,保證相關的值在任何時候訪問都是最新的。
#pragma warning
用來取消或者新增編譯時的警告資訊。每個警告資訊都會有個編號,如果warning CS01016之類的,使用的時候取CS後面的那個數字,例如:
#pragmawarning disable 414, 3021
這樣CS414和CS3021的警告資訊就都不會顯示了。
C# 3.0
型別推斷
申明變數的時候,可以不用直指定型別:
var i = 5;
var s = "Hello";
//兩種寫法是一樣的
int i = 5;
string s = "Hello";
型別推斷也支援陣列:
var b = new[] { 1, 1.5, 2, 2.5 }; // double[]
var c = new[] { "hello", null, "world” }; // string[]
擴充套件方法
擴充套件方法必須被定義在靜態類中,並且必須是非泛型、非巢狀的靜態類。例如:
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)
{
/**/
}
}
上面一個是給string型別的物件添加了一個方法,另一個是給所有型別的陣列添加了一個方法,方法有兩個整型引數。
擴充套件方法只在當前的名稱空間類有效,如果所在名稱空間被其它名稱空間import引用了,那麼在其它名稱空間中也有效。擴充套件方法的優先順序低於其它的常規方法,也就是說如果擴充套件方法與其它的方法相同,那麼擴充套件方法不會被呼叫。
Lamda表示式
可以看成是對匿名方法的一個語法上的簡化,但是λ表示式同時可以裝換為表示式樹型別。
物件和集合的初始化
var contacts = new List<Contact> {
new Contact {
Name = "Chris",
PhoneNumbers = { "123455", "6688" }
},
new Contact {
Name = "Jeffrey",
PhoneNumbers = { "112233" }
}
};
匿名型別
var p1 = new { Name = "Lawnmower", Price = 495.00 };
var p2 = new { Name = "Shovel", Price = 26.95 };
p1 = p2;
自動屬性
會自動生成一個後臺的私有變數
public Class Point
{
public int X { get; set; }
public int Y { get; set; }
}
查詢表示式
這個其實就是擴充套件方法的運用,編譯器提供了相關的語法便利,下面兩端程式碼是等價的:
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() })
表示式樹
Func<int,int> f = x => x + 1;
Expression<Func<int,int>> e = x => x + 1;
C# 4.0
協變和逆變
這個在C#2.0中就已經支援委託的協變和逆變了,C#4.0開始支援針對泛型介面的協變和逆變:
IList<string> strings = new List<string>();
IList<object> objects = strings;
協變和逆變僅針對引用型別。
動態繫結
看例子:
class BaseClass
{
public void print()
{
Console.WriteLine();
}
}
Object o = new BaseClass();
dynamic a = o;
//這裡可以呼叫print方法,在執行時a會知道自己是個什麼型別。 這裡的缺點在於編譯的時候無法檢查方法的合法性,寫錯的話就會出執行時錯誤。
a.print();
可選引數,命名引數
private void CreateNewStudent(string name, int studentid = 0, int year = 1)
這樣,最後一個引數不給的話預設值就是1,提供這個特性可以免去寫一些過載方法的麻煩。
呼叫方法的時候,可以指定引數的名字來給值,不用按照方法引數的順序來制定引數值:
CreateNewStudent(year:2, name:"Hima", studentid: 4); //沒有按照方法定義的引數順序
C# 5.0
1. 非同步程式設計
在.Net 4.5中,通過async和await兩個關鍵字,引入了一種新的基於任務的非同步程式設計模型(TAP)。在這種方式下,可以通過類似同步方式編寫非同步程式碼,極大簡化了非同步程式設計模型。如下式一個簡單的例項:
staticasyncvoidDownloadStringAsync2(Uriuri)
{
varwebClient =newWebClient();
varresult =awaitwebClient.DownloadStringTaskAsync(uri);
Console.WriteLine(result);
}
而之前的方式是這樣的:
staticvoidDownloadStringAsync(Uriuri)
{
varwebClient =newWebClient();
webClient.DownloadStringCompleted += (s, e) =>
{
Console.WriteLine(e.Result);
};
webClient.DownloadStringAsync(uri);
}
也許前面這個例子不足以體現async和await帶來的優越性,下面這個例子就明顯多了:
publicvoidCopyToAsyncTheHardWay(Streamsource,Streamdestination)
{
byte[] buffer =newbyte[0x1000];
Action<IAsyncResult> readWriteLoop =null;
readWriteLoop = iar =>
{
for(boolisRead = (iar ==null); ; isRead = !isRead)
{
switch(isRead)
{
casetrue:
iar = source.BeginRead(buffer, 0, buffer.Length,
readResult =>
{
if(readResult.CompletedSynchronously)return;
readWriteLoop(readResult);
},null);
if(!iar.CompletedSynchronously)return;
break;
casefalse:
intnumRead = source.EndRead(iar);
if(numRead == 0)
{
return;
}
iar = destination.BeginWrite(buffer, 0, numRead,
writeResult =>
{
if(writeResult.CompletedSynchronously)return;
destination.EndWrite(writeResult);
readWriteLoop(null);
},null);
if(!iar.CompletedSynchronously)return;
destination.EndWrite(iar);
break;
}
}
};
readWriteLoop(null);
}
publicasyncTaskCopyToAsync(Streamsource,Streamdestination)
{
byte[] buffer =newbyte[0x1000];
intnumRead;
while((numRead =awaitsource.ReadAsync(buffer, 0, buffer.Length)) != 0)
{
awaitdestination.WriteAsync(buffer, 0, numRead);
}
}
關於基於任務的非同步程式設計模型需要介紹的地方還比較多,不是一兩句能說完的,有空的話後面再專門寫篇文章來詳細介紹下。另外也可參看微軟的官方網站:Visual Studio Asynchronous Programming,其官方文件Task-Based Asynchronous Pattern Overview介紹的非常詳細, VisualStudio中自帶的CSharp Language Specification中也有一些說明。
2. 呼叫方資訊
很多時候,我們需要在執行過程中記錄一些調測的日誌資訊,如下所示:
publicvoidDoProcessing()
{
TraceMessage("Something happened.");
}
為了調測方便,除了事件資訊外,我們往往還需要知道發生該事件的程式碼位置以及呼叫棧資訊。在C++中,我們可以通過定義一個巨集,然後再巨集中通過__FILE__和__LINE__來獲取當前程式碼的位置,但C#並不支援巨集,往往只能通過StackTrace來實現這一功能,但StackTrace卻有不是很靠譜,常常獲取不了我們所要的結果。
針對這個問題,在.Net 4.5中引入了三個Attribute:CallerMemberName、CallerFilePath和CallerLineNumber。在編譯器的配合下,分別可以獲取到呼叫函式(準確講應該是成員)名稱,呼叫檔案及呼叫行號。上面的TraceMessage函式可以實現如下:
publicvoidTraceMessage(stringmessage,
[CallerMemberName]stringmemberName ="",
[CallerFilePath]stringsourceFilePath ="",
[CallerLineNumber]intsourceLineNumber = 0)
{
Trace.WriteLine("message: "+ message);
Trace.WriteLine("member name: "+ memberName);
Trace.WriteLine("source file path: "+ sourceFilePath);
Trace.WriteLine("source line number: "+ sourceLineNumber);
}
另外,在建構函式,解構函式、屬性等特殊的地方呼叫CallerMemberName屬性所標記的函式時,獲取的值有所不同,其取值如下表所示:
呼叫的地方 |
CallerMemberName獲取的結果 |
方法、屬性或事件 |
方法,屬性或事件的名稱 |
建構函式 |
字串 ".ctor" |
靜態建構函式 |
字串 ".cctor" |
解構函式 |
該字串 "Finalize" |
使用者定義的運算子或轉換 |
生成的名稱成員,例如, "op_Addition"。 |
特性建構函式 |
特性所應用的成員的名稱 |
例如,對於在屬性中呼叫CallerMemberName所標記的函式即可獲取屬性名稱,通過這種方式可以簡化INotifyPropertyChanged介面的實現。
C# 6.0
1、自動屬性的增強
1.1、自動屬性初始化 (Initializers for auto-properties)
C#4.0下的果斷實現不了的。
C#6.0中自動屬性的初始化方式
只要接觸過C#的肯定都會喜歡這種方式。真是簡潔方便呀。
1.2、只讀屬性初始化Getter-only auto-properties
先來看一下我們之前使用的方式吧
public class Customer { public string Name { get; } public Customer(string firstName,string lastName) { Name = firstName +" "+ lastName; } }
再來看一下C#6.0中
public class Customer { public string FirstName { get; }="aehyok"; public string LastName { get; }="Kris"; }
和第一條自動屬性初始化使用方式一致。
2、Expression bodiedfunctionmembers
2.1 用Lambda作為函式體Expression bodies on method-like members
public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
再來舉一個簡單的例子:一個沒有返回值的函式
public void Print() => Console.WriteLine(FirstName + " " + LastName);
2.2、Lambda表示式用作屬性Expression bodies on property-like function members
public override string ToString() { return FirstName + " " + LastName; }
現在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、引用靜態類Using Static
在Using中可以指定一個靜態類,然後可以在隨後的程式碼中直接使用靜態的成員
4、空值判斷Null-conditional operators
直接來看程式碼和執行結果
通過結果可以發現返回的都為null,再也不像以前那樣繁瑣的判斷null勒。
5、字串嵌入值
在字串中嵌入值
之前一直使用的方式是
現在我們可以簡單的通過如下的方式進行拼接
6、nameof表示式nameofexpressions
在方法引數檢查時,你可能經常看到這樣的程式碼(之前用的少,這次也算學到了)
public static void AddCustomer(Customer customer) { if (customer == null) { throw new ArgumentNullException("customer"); } }
裡面有那個customer是我們手寫的字串,在給customer改名時,很容易把下面的那個字串忘掉,C#6.0 nameof幫我們解決了這個問題,看看新寫法
public static void AddCustomer(Customer customer) { if (customer == null) { throw new ArgumentNullException(nameof(customer)); } }
7、帶索引的物件初始化器Index initializers
直接通過索引進行物件的初始化,原來真的可以實現
通過這種方式可以發現字典中只有三個元素,所以也就只有這三個索引可以訪問額,其他型別的物件和集合也是可以通過這種方式進行初始化的,在此就不進行一一列舉了。
8、異常過濾器 (Exception filters)
先來看一個移植過來的方法
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"); } }
在微軟的文件中還給出了另一種用法,這個異常會在日誌記錄失敗時拋給上一層呼叫者
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關鍵字是不能出現在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、無引數的結構體建構函式—— Parameterless constructors in structs
C# 7.0
1.out 變數(out variables)
以前我們使用out變數必須在使用前進行宣告,C# 7.0 給我們提供了一種更簡潔的語法 “使用時進行內聯宣告” 。如下所示:
1 var input = ReadLine(); 2 if (int.TryParse(input, out var result)) 3 { 4 WriteLine("您輸入的數字是:{0}",result); 5 } 6 else 7 { 8 WriteLine("無法解析輸入..."); 9 }
上面程式碼編譯後:
1 int num; 2 string s = Console.ReadLine(); 3 if (int.TryParse(s, out num)) 4 { 5 Console.WriteLine("您輸入的數字是:{0}", num); 6 } 7 else 8 { 9 Console.WriteLine("無法解析輸入..."); 10 }
原理解析:所謂的 “內聯宣告” 編譯後就是以前的原始寫法,只是現在由編譯器來完成。
備註:在進行內聯宣告時,即可直接寫明變數的型別也可以寫隱式型別,因為out關鍵字修飾的一定是區域性變數。
2. 元組(Tuples)
元組(Tuple)在 .Net 4.0 的時候就有了,但元組也有些缺點,如:
1)Tuple 會影響程式碼的可讀性,因為它的屬性名都是:Item1,Item2.. 。
2)Tuple 還不夠輕量級,因為它是引用型別(Class)。
備註:上述所指 Tuple 還不夠輕量級,是從某種意義上來說的或者是一種假設,即假設分配操作非常的多。
C# 7 中的元組(ValueTuple)解決了上述兩個缺點:
1)ValueTuple 支援語義上的欄位命名。
2)ValueTuple 是值型別(Struct)。
1. 如何建立一個元組?
1 var tuple = (1, 2); // 使用語法糖建立元組 2 var tuple2 = ValueTuple.Create(1, 2); // 使用靜態方法【Create】建立元組 3 var tuple3 = new ValueTuple<int, int>(1, 2); // 使用 new 運算子建立元組 4 5 WriteLine($"first:{tuple.Item1}, second:{tuple.Item2}, 上面三種方式都是等價的。");
原理解析:上面三種方式最終都是使用 new 運算子來建立例項。
2. 如何建立給欄位命名的元組?
1 // 左邊指定欄位名稱 2 (int one, int two) tuple = (1, 2); 3 WriteLine($"first:{tuple.one}, second:{tuple.two}"); 4 5 // 右邊指定欄位名稱 6 var tuple2 = (one: 1, two: 2); 7 WriteLine($"first:{tuple2.one}, second:{tuple2.two}"); 8 9 // 左右兩邊同時指定欄位名稱 10 (int one, int two) tuple3 = (first: 1, second: 2); /* 此處會有警告:由於目標型別(xx)已指定了其它名稱,因為忽略元組名稱xxx */ 11 WriteLine($"first:{tuple3.one}, second:{tuple3.two}");
注:左右兩邊同時指定欄位名稱,會使用左邊的欄位名稱覆蓋右邊的欄位名稱(一一對應)。
原理解析:上述給欄位命名的元組在編譯後其欄位名稱還是:Item1, Item2...,即:“命名”只是語義上的命名。
3. 什麼是解構?
解構顧名思義就是將整體分解成部分。
4. 解構元組,如下所示:
1 var (one, two) = GetTuple(); 2 3 WriteLine($"first:{one}, second:{two}");
1 static (int, int) GetTuple() 2 { 3 return (1, 2); 4 }
原理解析:解構元組就是將元組中的欄位值賦值給宣告的區域性變數(編譯後可檢視)。
備註:在解構時“=”左邊能提取變數的資料型別(如上所示),元組中欄位型別相同時即可提取具體型別也可以是隱式型別,但元組中欄位型別
不相同時只能提取隱式型別。
5. 解構可以應用於 .Net 的任意型別,但需要編寫Deconstruct 方法成員(例項或擴充套件)。如下所示:
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}");
原理解析:編譯後就是由其例項呼叫Deconstruct 方法,然後給區域性變數賦值。
Deconstruct 方法簽名:
1 // 例項簽名 2 public void Deconstruct(out type variable1, out type variable2...) 3 4 // 擴充套件簽名 5 public static void Deconstruct(this type instance, out type variable1, out type variable2...)
總結:1. 元組的原理是利用了成員型別的巢狀或者是說成員型別的遞迴。2. 編譯器很牛B才能提供如此優美的語法。
使用 ValueTuple 則需要匯入: Install - Package System.ValueTuple
3.模式匹配(Patternmatching)
1. is 表示式(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 結合使用 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 條件控制語句(obj is type variable) 2 { 3 // Processing... 4 }
原理解析:此 is 非彼 is ,這個擴充套件的 is 其實是 as 和 if 的組合。即它先進行 as 轉換再進行 if 判斷,判斷其結果是否為 null,不等於 null 則執行
語句塊邏輯,反之不行。由上可知其實C# 7之前我們也可實現類似的功能,只是寫法上比較繁瑣。
2. switch語句更新(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: // 型別模式匹配 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): // 型別模式匹配 + 條件表示式 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("未知的型別"); 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,編譯後你會發現擴充套件的 switch 就是 as 、if 、goto 語句的組合體。同 isexpressions 一樣,以前我們也能實
現只是寫法比較繁瑣並且可讀性不強。
總結:模式匹配語法是想讓我們在簡單的情況下實現類似與多型一樣的動態呼叫,即在執行時確定成員型別和呼叫具體的實現。
4. 區域性引用和引用返回 (Ref locals and returns)
我們知道 C# 的 ref 和 out 關鍵字是對值傳遞的一個補充,是為了防止值型別大物件在Copy過程中損失更多的效能。現在在C# 7中ref 關鍵字得
到了加強,它不僅可以獲取值型別的引用而且還可以獲取某個變數(引用型別)的區域性引用。如:
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) 宣告方法簽名時必須在返回型別前加上 ref 修飾。
b) 在每個 return 關鍵字後也要加上 ref 修飾,以表明是返回引用。
2. 分配引用(即賦值),必須在宣告區域性變數前加上 ref 修飾,以及在方法返回引用前加上 ref 修飾。
注:C# 開發的是託管程式碼,所以一般不希望程式設計師去操作指標。並由上述可知在使用過程中需要大量的使用 ref 來標明這是引用變數(編譯後其
實沒那麼多),當然這也是為了提高程式碼的可讀性。
總結:雖然 C# 7 中提供了局部引用和引用返回,但為了防止濫用所以也有諸多約束,如:
1. 你不能將一個值分配給 ref 變數,如:
1 ref int num = 10; // error:無法使用值初始化按引用變數
2. 你不能返回一個生存期不超過方法作用域的變數引用,如:
1 public ref int GetLocalRef(int num) => ref num; // error: 無法按引用返回引數,因為它不是 ref 或 out 引數
3. ref 不能修飾 “屬性” 和 “索引器”。
1 var list = new List<int>(); 2 ref var n = ref list.Count; // error: 屬性或索引器不能作為 out 或 ref 引數傳遞
原理解析:非常簡單就是指標傳遞,並且個人覺得此語法的使用場景非常有限,都是用來處理大物件的,目的是減少GC提高效能。
5.區域性函式(Local functions)
C# 7 中的一個功能“區域性函式”,如下所示:
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 [資料型別,void] 方法名([引數]) 2 { 3 // Method body;[] 裡面都是可選項 4 }
原理解析:區域性函式雖然是在其他函式內部宣告,但它編譯後就是一個被 internal 修飾的靜態函式,它是屬於類,至於它為什麼能夠使用上級函
數中的區域性變數和引數呢?那是因為編譯器會根據其使用的成員生成一個新型別(Class/Struct)然後將其傳入函式中。由上可知則區域性函式的聲
明跟位置無關,並可無限巢狀。
總結:個人覺得區域性函式是對 C# 異常機制在語義上的一次補充(如上例),以及為程式碼提供清晰的結構而設定的語法。但區域性函式也有其缺點,
就是區域性函式中的程式碼無法複用(反射除外)。
6. 更多的表示式體成員(More expression-bodied members)
C# 6 的時候就支援表示式體成員,但當時只支援“函式成員”和“只讀屬性”,這一特性在C# 7中得到了擴充套件,它能支援更多的成員:建構函式
、解構函式、帶 get,set 訪問器的屬性、以及索引器。如下所示:
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 }
備註:索引器其實在C# 6中就得到了支援,但其它三種在C# 6中未得到支援。
7. Throw 表示式(Throw expressions)
異常機制是C#的重要組成部分,但在以前並不是所有語句都可以丟擲異常的,如:條件表示式(? :)、null合併運算子(??)、一些Lambda
表示式。而使用 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("引數不合法") : value; 11 } 12 13 static string GetName() => null; 14 }
8. 擴充套件非同步返回型別(Generalized async return types)
以前非同步的返回型別必須是:Task、Task<T>、void,現在 C# 7 中新增了一種型別:ValueTask<T>,如下所示:
1 public async ValueTask<int> Func() 2 { 3 await Task.Delay(3000); 4 return 100; 5 }
總結:ValueTask<T> 與 ValueTuple 非常相似,所以就不列舉: ValueTask<T> 與 Task 之間的異同了,但它們都是為了優化特定場景效能而
新增的型別。
使用 ValueTask<T> 則需要匯入: Install - Package System.Threading.Tasks.Extensions
9. 數字文字語法的改進(Numeric literal syntax improvements)
C# 7 還包含兩個新特性:二進位制文字、數字分隔符,如下所示:
1 var one = 0b0001; 2 var sixteen = 0b0001_0000; 3 4 long salary = 1000_000_000; 5 decimal pi = 3.141_592_653_589m;
注:二進位制文字是以0b(零b)開頭,字母不區分大小寫;數字分隔符只有三個地方不能寫:開頭,結尾,小數點前後。
總結:二進位制文字,數字分隔符 可使常量值更具可讀性。
2-6 處處:http://www.cnblogs.com/zq20/p/6323205.html
7 出處:http://www.cnblogs.com/VVStudy/