C#-新特性筆記
C#新特性筆記
2017年,釋出了C#7.0。公司進行了一次考試,本人在考試結束後回顧試卷,經過查閱和彙總,寫了一份答題解析,並分享給同事們,原為word文件。在整理磁碟時找到了該檔案,現重新整理如下。大部分內容甚至因為基本用不到已經遺忘,也算是重新回顧一下吧。
解析:
屬性,像資料成員一樣被訪問,像方法(函式)一樣被實現。
C#將屬性從一些特殊習慣提升為第一等的語言特性。是對訪問或者修改內部資料的方法的擴充套件。
C#2.0:常規屬性,可以對get/set指定不同級別的訪問修飾符,使你對資料成員的可見性有更好的控制。
C#3.0:出現了自動實現的屬性(Auto-Implemented Properties)。
(1) 依舊需要使用訪問修飾符給get訪問器或者set訪問器以控制訪問許可權。
(2) 必須同時實現set訪問器和get訪問器,缺一不可。
(3) 自動實現的屬性,編譯器在執行時會自動生成一個私有的欄位,這個自動生成的欄位不能直接訪問。
(4) 當需要實現對資料的合法性驗證或者其它特殊處理的時候不能用自動實現的屬性。
(5) 要初始化就必須要在構造函式中去完成初始值的設定。
C#6.0:自動實現屬性的優化。
可以用如下寫法: public int ReadOnlyAge {get;} = 30; // 唯讀的也可以初始設定
Readonly:是可在欄位宣告上使用的修飾符。與const關鍵字類似使用,但是const為編譯時常量,readonly為執行時常量。編譯時常量效能較好,執行時常量靈活性較好。
解析:
VAR是3.0出的一個弱化型別(隱式型別)的定義。其類似object但是效率比object高點,因為在執行前就會被編譯器推斷出其型別。主要用於在宣告變數時,無法確定資料型別使用的。可理解為宣告變數的佔位符。
用法總結:
(1)必須在定義時初始化。也就是必須是var s=”asd”,而不能是:var s;s=”asd”。因為編譯器會根據初始值來推斷出具體的型別。
(2)無法將null賦值給隱式(var)型別化的區域性變數。
(3) 初始化不能是一個匿名方法(匿名委託、匿名函式)。
var t = delegate(int i) { return i; };
//報錯:無法將"匿名方法"賦予隱式型別的區域性變數
(4)可以用匿名類初始化,但是屬性一定要賦初始值。
var person = new { Age = 18, Name = "Kobe" };
//正確
var person = new { Age, Name };
//報錯:當前上下文中不存在"Age" 當前上下文中不存在"Name"
(5)一旦初始化完成,就不能再給變數賦予與初始化值型別不同的值了。
(6)Var宣告的必須是區域性變數(不包括類級別的變數)。Var定義必須是在方法中,或者屬性get、set訪問器中。
(7)使用var定義變數和object不同,它在效率使用上和強型別方式定義變數一樣。
(8)Var不能用作方法的引數。
public void Test(var t){}
//報錯:上下文關鍵字"var"只能出現在區域性變數宣告中
(9)Var不能當作返回值型別。
public var Test(){}
//報錯:並非所有程式碼路徑都返回值 或者:上下文關鍵字"var"只能出現在區域性變數宣告中
解析:
Lambda表示式:一種可用於建立委託或表示式目錄樹型別的匿名函式。
C#2.0:引入了匿名方法。
C#3.0:引入了Lambda表示式,取代了匿名方法,作為編寫內聯程式碼的首選方式。
Lambda表示式往往是學習Linq的基礎。
System.Linq:
SELECT:
//將序列中的每個元素投影到新表中。返回元素為對source 的每個元素呼叫轉換函式的結果。
IEnumerable<TResult> Select<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
WHERE:
//基於謂詞篩選值序列。返回包含輸入序列中滿足條件的元素。
IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
ALL:
//確定序列中的所有元素是否滿足條件。如果源序列中的每個元素都通過指定謂詞中的測試,或者序列為空,則為 true;否則為 false。
Bool All<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
ANY:
//確定序列中的任何元素是否都滿足條件。如果源序列中的任何元素都通過指定謂詞中的測試,則為 true;否則為 false。
bool Any<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);
Func:
Func是一個委託。封裝一個具有零個或多個指定型別的引數並返回一個指定型別的結果值的方法。
解析:
Linq:語言整合查詢。Language Integrated Queryd 的簡稱。C# 3.0 中加入的新特性。
程式集 | 名稱空間 | 描述 | |
---|---|---|---|
LINQ to Objects | System.Core.dll | System.Linq | 提供對記憶體中集合操作的支援 |
LINQ to XML | System.Xml.Linq.dll | System.Xml.Linq | 提供對XML資料來源的操作的支援 |
LINQ to SQL | System.Data.Linq.dll | System.Data.Linq | 提供對Sql Server資料來源的操作的支援。(微軟宣佈已不再更新,推薦使用LINQ to Entities) |
LINQ to DataSet | System.Data.DataSetExtensions.dll | System.Data | 提供對離線資料操作的支援 |
LINQ to Entities | System.Core.dll; System.Data.Entity.dll | System.Linq;System.Data.Objects | Linq to Entities是Entity Framework的一部分並且取代Linq to SQL作為在資料庫上使用Linq的標準機制。(Entity Framework是由微軟釋出的開源物件-關係對映(ORM)框架,支援多種資料庫) |
LINQ查詢表示式:
關鍵字 | 功能 |
---|---|
約束 | Linq查詢表示式必須以from子句開頭,以select或者group子句結束 |
from…in… | 指定要查詢的資料來源以及範圍變數,多個from子句則表示從多個數據源查詢資料。注意:C#編譯器會把“複合from子句“的查詢表示式轉換為SelectMany()擴充套件方法。 |
join…in…on…equals… | 指定多個數據源的關聯方式 |
Let | 引入用於儲存查詢表示式中子表示式結果的範圍變數 |
Orderby、desceding | 指定元素的排序欄位和排序方式。當有多個排序欄位時,由欄位順序確定主次關係,可指定升序和降序。 |
where | 指定元素的篩選條件。多個where子句則表示了並列條件,必須全部都滿足了才能入選。每個where子句可用謂詞&&、 |
group | 指定元素的分組欄位。 |
select | 制定查詢要返回的目標資料,可指定任何型別,甚至是匿名型別。 |
into | 提供一個臨時的識別符號。該標識可以引用join、group和select子句的結果。(1)直接出現在join子句之後的into關鍵字會被翻譯為GroupJoin。(into之前的查詢變數可以繼續使用)(2)Select或group子句之後的into關鍵字它會重新開始一個查詢,我們可以繼續引入where、orderby、select子句,它是對分步構建查詢表示式的一種簡寫方式。(into之前的查詢變數都不可再使用) |
解析:
Dynamic是C#4.0的新特性,一個靜態型別,但是在編譯時會跳過靜態型別的檢查。 在本質上它與Var型別是有區別的。
通過IL程式碼驗證可以知道dynamic變數是一個object變數。Long型別就是int64。
解析:
C#4.0增加新特性,元組Tuple,它是一種固定成員的泛型集合。
.NET Framework 直接支援具有 1 到 7 元素的元組。 此外,可以建立由巢狀中的元組物件的元組的八個或多個元素Rest屬性Tuple<T1, T2, T3, T4, T5, T6, T7, TRest>物件。
前7個元素寫法為:Tuple.Item1, Tuple.Item2…Tuple.Item7。
然而第8個及以後的元素呼叫寫法為:Tuple.Rest.Item1, Tuple.Rest.Item2…
不足:
(1)訪問元素的時候只能通過ItemX去訪問,使用前需要明確元素順序,屬性名字沒有實際意義,不方便記憶;
(2)最多有八個元素,要想更多隻能通過最後一個元素進行巢狀擴充套件;
(3)Tuple是一個引用型別,不像其它的簡單型別一樣是值型別,它在堆上分配空間,在CPU密集操作時可能有太多的建立和分配工作。
C# 7.0 增加新特性,值元組ValueTuple,.Net Framework 4.7以上版本可用。
值元組也是一種資料結構,用於表示特定數量和元素序列,但是和元組類的主要區別為:
(1)值元組是結構,是值型別,不是類,而元組(Tuple)是類,引用型別;
(2)值元組元素是可變的,不是隻讀的,也就是說可以改變值元組中的元素值;
(3)值元組的資料成員是欄位不是屬性。
優化區別:
(1)當構造出超過7個元素以上的值元組後,可以使用接下來的ItemX進行訪問巢狀元組中的值,對於上面的例子,要訪問第十個元素,既可以通過testTuple10.Rest.Item3訪問,也可以通過testTuple10.Item10來訪問:
var testTuple10 = new ValueTuple<int, int, int, int, int, int, int, ValueTuple<int, int, int>>(1, 2, 3, 4, 5, 6, 7, new ValueTuple<int, int, int>(8, 9, 10));
Console.WriteLine($"Item 10: {testTuple10.Rest.Item3}, Item 10:{testTuple10.Item10}");
(2)返回值可以不明顯指定ValueTuple,使用新語法(,)代替,如(string, int, uint):
static (string, int, uint) GetStudentInfo1(string name)
{
return ("Bob", 28, 175);
}
static void RunTest1()
{
var studentInfo = GetStudentInfo1("Bob");
Console.WriteLine($"Student Information: Name [{studentInfo.Item1}], Age [{studentInfo.Item2}], Height [{studentInfo.Item3}]");
}
(3)返回值可以指定元素名字,方便理解記憶賦值和訪問:
static(string name, int age, uint height) GetStudentInfo1(string name)
{
return ("Bob", 28, 175);
}
static void RunTest1()
{
var studentInfo = GetStudentInfo1("Bob");
Console.WriteLine($"Student Information: Name [{studentInfo.name}],
Age [{studentInfo.age}], Height [{studentInfo.height}]");
}
解析:
C#6.0新功能:Static Using Statements。
注意:只會引入指定類的擴充套件方法 (Extension Method),而不是引入某名稱空間下的全部擴充套件方法。
A選項:沒毛病。
B選項:Console是system空間下的,必須要引用system才行。
//報錯:當前上下文中不存在Console。
C選項:WriteLine是System.Console下的,都已經Static了,沒毛病。
D選項:都引用了,怎麼寫都沒毛病。
解析:
C#6.0 新功能:Nameof Expressions 故名思義就是取得名字的表示式。
用於獲取變數、型別或成員的簡單(非限定)字串名稱。
解析:
C#7.0:C#7.0 中引入了引用返回(ref return)的概念,允許C#方法中返回一個值型別的引用。
這樣,我們通過C#7.0,能直接將呼叫棧(call stack)上的引用返回。
並且,對於體積較大的結構體(struct),返回引用比傳遞結構值要快很多,因為結構體的賦值會對整個結構進行拷貝。
另外需要注意的是,ref return的引用,在語言層面附加規則,不允許返回方法內的區域性變數的引用,換句話說,被返回的堆疊地址,必須低於當前方法的入口地址。
感覺比較繞,這個7.0的優化貌似受眾面不廣。
解析:
C#7.0:允許_出現,作為數字分隔符。
Var a = 123_466;
Var x = 0*AB_CD_F;
可以將 _ 放入任意的數字之間,以提高可讀性,它們對值沒有影響。
此外,C#7.0 引入了二進位制文字,這樣你就可以指定二進位制模式而不用去了解十六進位制。
Var b = 0b1010_1011_1100_1101_1110_1111;
解析:
C#3.0:初始化列表,在物件初始化時順便將成員屬性初始化。
其中,若類的建構函式用的是預設建構函式,即沒有引數的建構函式,初始化列表前的小括號“()“是可以去掉的。
分號一般用於語句結束完成或函式呼叫宣告和定義自變數結束。
解析:
C#4.0:迎合多核處理的發展。它引入了一個新概念—任務(Task),作為支援並行運算的重要組成部分,同時也作為對執行緒池的一個補充和完善。
(1)使用建構函式建立Task:
Task t1 = new Task(MyMethod);
t1.Start();
(2)使用Task.Factory.StartNew進行建立Task:
Task t1 = Task.Factory.StartNew(MyMethod);
總結:任務給了我們更多的方便性、靈活性的同時,也帶來了比執行緒池更多的資源消耗。如果想減少資源消耗,請直接使用執行緒池QueueUserWorkItem方法效果會更好;如果想要更多的控制與靈活性,任務(Task)是不二的選擇。
解析:
同上單選(1)。
目前就兩種方式可以賦值,1是構造方法2是屬性初始化時。
解析:
同上多選(13),單選(1)。
解析:
Lambda表示式:同上單選(3)。
Lambda表示式的格式: (引數列表)=>表示式或語句塊
Lambda表示式使用的示例(以下均為合法格式,注意小括號()和花括號{}的是否必須)
x, y) => x * y | 多引數,隱式型別=> 表示式 |
x => x * 5 | 單引數, 隱式型別=>表示式 |
x => { return x * 5; } | 單引數,隱式型別=>語句塊 |
(int x) => x * 5 | 單引數,顯式型別=>表示式 |
(int x) => { return x * 5; } | 單引數,顯式型別=>語句塊 |
() => Console.WriteLine | 無引數 |
解析:
C#6.0:優化索引初始化器(Index Initializers)。
之前:
現在:
解析:
C#6.0中新增了與異常相關的特性。使得我們可以在catch和finally中等待非同步方法。
Await:運算子在非同步方法應用於任務,以掛起執行方法,直到所等待的任務完成。
解析:
C#6.0:異常篩選器(Exception)。VB和F#中早就有的功能。
可以使用同一 try-catch 語句中的多個特定 catch 子句。 在這種情況下,catch 子句的順序很重要,因為 catch 子句是按順序檢查的。 在使用更籠統的子句之前獲取特定性更強的異常。 如果捕獲塊的排序使得永不會達到之後的塊,則編譯器將產生錯誤。
篩選想要處理的異常的一種方式是使用 catch 引數。 也可以使用謂詞表達式進一步檢查該異常以決定是否要對其進行處理。 如果謂詞表達式返回 false,則繼續搜尋處理程式。
疑惑(待解決): 搜尋出現了catch ()if()這樣的用法,但是明顯不能用,那麼他們出現的原因是什麼?是因為老方法方式現在被去掉了還是從VB或者F#那邊直接copy的程式碼?導致了人云亦云?
在微軟的官方資料中我獲得的以及自身驗證過的可用情況是這樣的:
MicrosoftDocs:
catch (ArgumentException e) when (e.ParamName == "…")
{
我的實驗結果:
然而在網路上卻有大量這種文件:
摘自網路部落格:
摘自網路部落格:
相關連線:
疑惑之參考一
疑惑之參考二
疑惑之參考三
解析:
Lambda表示式:同上單選(3)、多選(15)。
Lambda表示式使用限制:
(1)在 is 或 as 運算子的左側不允許使用 Lambda。
(2)適用於匿名方法的所有限制也適用於 Lambda 表示式。
匿名方法使用限制:
(1)匿名方法的引數的範圍是“匿名方法塊”。
(2)如果目標在塊外部,那麼,在匿名方法塊內使用跳轉語句(如 goto、break 或 continue)是錯誤的。 如果目標在塊內部,在匿名方法塊外部使用跳轉語句(如 goto、break 或 continue)也是錯誤的。
(3)匿名方法不能訪問外部範圍的 ref 或 out 引數。
(4)在“匿名方法塊”中不能訪問任何不安全程式碼。
(5)在 is 運算子的左側不允許使用匿名方法。