本文來告訴你們在C#不多有人會發現的科技。即便是工做了好多年的老司機也不必定會知道這些科技,若是以爲我是在騙你,那麼請看看本文的內容。web
本來最初 C# 的設計是簡單和高效開發的,在通過了這麼多年衆多公司和開發者的努力下,整個 C# 裏面包含了大量有趣的功能。其中一部分功能是針對於某些特殊需求設計的,例如高性能或高併發或無內存回收等。在通過了 10 多年的迭代,不多人能徹底瞭解整個 C# 語言和框架級作了哪些有趣的功能面試
我在網上找了不少大神的博客,而後和不少大神聊天,知道了一些科技,因而就在本文和你們分享一下。若是你們有了解本博客裏面沒有收藏的科技,還請告訴我express
如今整個 C# 從編譯器到運行時都是開源的,全部權在 dotnet 基金會上,所有開源的項目都基於最友好的 MIT 協議和 Apache 2 開源協議,文檔協議遵循CC-BY協議。這將容許任何人任何組織和企業任意處置,包括使用,複製,修改,合併,發表,分發,再受權,或者銷售。惟一的限制是,軟件中必須包含上述版 權和許可提示,後者協議將會除了爲用戶提供版權許可以外,還有專利許可,而且受權是免費,無排他性的(任何我的和企業都能得到受權)而且永久不可撤銷,用戶使用.NET 和 C# 徹底不用擔憂收費問題和版權問題,以及後續沒法維護問題。而 dotnet 基金會是一個開放的平臺,我也是 dotnet 基金會的成員之一。微軟在 2020 的時候依然是 dotnet 基金會最大的支持組織安全
如今最火的 dotnet 倉庫是 dotnet csharplang 倉庫,當前的 C# 語言特性由整個社區決定,這是一個官方開放用來討論 C# 語言將來的倉庫,每天都有大佬們在討論語言的特性,歡迎你們加入併發
接下來讓我告訴你們一些不多有人會發現的科技框架
無限級判斷空
在 C# 6.0 可使用??
判斷空,那麼就可使用下面代碼ide
var v1 = "123"; string v2 = null; string v3 = null; var v = v1 ?? v2 ?? v3;
實際上能夠無限的使用??
判斷前面一個函數爲空,那麼問題來了,下面的代碼輸出的是多少?函數
var n = 2 + foo?.N ?? 1;
上面代碼的 foo 就是空的,那麼 n 是多少?是 1 仍是 2 仍是 3 仍是空?高併發
想要了解這道題的推導過程請看C# 高級面試題 裏面寫了不少老司機都不必定能解出性能
使用 using 關鍵詞省略長的定義
例若有下面這個代碼,在這個代碼裏面使用了不少的 List 嵌套,以下面代碼所示裏面有不少定義的代碼
var foo = new System.Collections.Generic.Dictionary<System.Collections.Generic.List<System.Collections.Generic.List<string>>, string>();
能夠看到上面代碼中,有大量的代碼都是用來做爲類型的定義,假設這個值做爲某個方法的參數,那纔是可怕
一個簡單的方法是使用 using 關鍵詞,如在文件的開頭添加以下代碼
using HvcnrclHnlfk = System.Collections.Generic.Dictionary<System.Collections.Generic.List<System.Collections.Generic.List<string>>,string>;
在添加了上面代碼以後,在這個文件裏的全部用到如上面很長的定義的代碼均可以使用 using
後面的值能夠代替,如本文上面使用了 HvcnrclHnlfk
這個詞,來看看替換以後的代碼長度
var foo = new HvcnrclHnlfk();
辣麼大
實際上寫到這裏我有些很差意思,好像剛剛說的都是你們都知道的,那麼我就要開始寫你們不多知道的科技
等等,什麼是 辣麼大
大哇?其實這是 lambda 表達式的翻譯
請看看下面這段有趣的代碼
Func<string,string, EventHandler> foo = (x, y) => (s, e) => { var button = (Button) s; button.Left = x; button.Top = y; }; Button1.Click += foo(0, -1);
上面的代碼經過一個 lambda 表達式返回一個另外一個 lambda 表達式,或者說用一個委託返回另外一個委託。這是一個特別有趣的寫法,經過函數返回函數的思想能夠用來寫出一些有趣的邏輯,特別是在多層嵌套的時候
固然使用委託但是會出現另外一個問題的,請問下面的代碼實際調用的是哪一個委託,下面代碼的 a 和 b 和 c 都是 Action
委託,同時都不是空的
((a + b + c) - (a + c))();
在數學上,其實函數也能夠視爲變量,頗有科技範的 C# 固然也支持如此的功能,將函數包裝爲委託的時候,可讓委託自己支持加減法哦,只是這個加減法的規則有些詭異。不信,請猜猜上面代碼執行了什麼函數
衝突的類型
在遇到某些類型,特別是放在 NuGet 上的多個不一樣的庫裏面的類型,這些類型有相同的類名,如 Data 或 Control 等很通用的命名的時候,在代碼中若是須要同時使用這兩個類,就須要補全整個命名空間,以下面代碼
var webControl = new System.Web.UI.WebControls.Control(); var formControl = new System.Windows.Forms.Control();
若是常用這兩個控件,那麼就須要寫不少補全命名空間的代碼,代碼不少。好在微軟的大佬們給出了一個坑方法,使用這個方法能夠不寫命名空間,或者說只須要在文件開始 using 一次,請看代碼
using web = System.Web.UI.WebControls; using win = System.Windows.Forms; web::Control webControl = new web::Control(); win::Control formControl = new win::Control();
參見:https://stackoverflow.com/a/9099/6116637
extern alias
若是使用了兩個不一樣的程序集放在兩個不一樣的 dll 文件裏面,這兩個程序集都有相同命名空間和類型,那麼如何使用指定的庫
以下面代碼所示,在兩個 dll 裏面都定義了 F.Foo
類型
//a.dll namespace F { public class Foo { } } //b.dll namespace F { public class Foo { } }
這時就可使用 extern alias 關鍵詞
參見:C#用extern alias解決兩個assembly中相同的類型全名 - fresky - 博客園
字符串
你們看到了 C# 6.0 的$
,是否是能夠和@
一塊兒?
var str = "kktpqfThiq"; string foo = $@"換行{str}";
注意兩個的順序,反過來直接告訴你代碼不能這樣寫
此知識點再也不適用,由於在 C# 8.0 的時候,能夠按照任意的順序使用 $
和 @
標記。詳細請看 $ - 字符串內插 - C# 參考 特別感謝 592844340 羣內熱心人員勘誤
特殊關鍵字
實際上有下面幾個關鍵字是沒有詳細的文檔,可能只有微軟的編譯器才知道
__makeref __reftype __refvalue __arglist
不過在 C# 7.2 可使用其餘的關鍵字作到一些功能,詳細請看個人 C# 7.0 博客
使用 Unions (C++ 同樣的)
若是看到 C++ 可使用內聯,不要說 C# 沒有這個功能,實際上也可使用 FieldOffset 特性實現和 C++ 同樣的內聯的功能 ,請看下面代碼
[StructLayout(LayoutKind.Explicit)] public class A { [FieldOffset(0)] public byte One; [FieldOffset(1)] public byte Two; [FieldOffset(2)] public byte Three; [FieldOffset(3)] public byte Four; [FieldOffset(0)] public int Int32; }
以下面代碼就定義了int
變量,修改這個變量就是修改其餘的三個變量
static void Main(string[] args) { A a = new A { Int32 = int.MaxValue }; Console.WriteLine(a.Int32); Console.WriteLine("{0:X} {1:X} {2:X} {3:X}", a.One, a.Two, a.Three, a.Four); a.Four = 0; a.Three = 0; Console.WriteLine(a.Int32); }
運行代碼能夠看到輸出以下
2147483647 FF FF FF 7F 65535
能夠看到修改其中某個值都會相互影響,這幾個值共用了相同的一個內存空間
接口默認方法
實際上能夠給接口使用默認方法,使用的方式以下
public static void Foo(this IF1 foo) { //實際上你們也看到是如何定義 }
固然了,在 C# 8.0 還有更直接的方法,詳細請看 在 C# 中使用默認接口方法安全地更新接口
stackalloc
不少人都不知道這個科技,這是不安全代碼,從棧申請空間
int* block = stackalloc int[100];
使用的時候須要當心你的棧也許會炸掉
參見:stackalloc
指定編譯
這個是一個有趣的特性實現的功能,是一個編譯器技術,寫給編譯器看的特性。使用 Conditional 特性可讓代碼在指定條件不使用,以下面的代碼,規定了只有在 DEBUG 宏定義的時候才讓 F2 方法生效。所以在 Release 下就不會使用 F2 方法了
public sealed clas Foo { public Foo F1() { Console.WriteLine("進入F1"); return this; } [Conditional("DEBUG")] public void F2() { Console.WriteLine("F2"); } }
簡單讓代碼跑一下
static void Main(string[] args) { var foo = new Foo(); foo.F1(); foo.F2(); }
結果是什麼,你們也知道,在 Debug 和 Release 輸出是不相同。可是這麼簡單的怎麼會在這裏說呢,請你們看看這個代碼輸出什麼
static void Main(string[] args) { var foo = new Foo(); foo.F1().F2(); }
實際上在 Release 下什麼都不會輸出,此時的 F1 不會被執行
true 判斷
下面寫個見鬼的代碼
var foo = new Foo(10); if (foo) { Console.WriteLine("個人類沒有繼承 bool ,竟然能夠這樣寫"); }
沒錯 Foo 沒有繼承 bool 竟然能夠這樣寫
實際上就是重寫 true 方法,請看代碼
public class Foo { public Foo(int value) { _count = value; } private readonly int _count; public static bool operator true(Foo mt) { return mt._count > 0; } public static bool operator false(Foo mt) { return mt._count < 0; } }
是否是以爲不少有人這樣寫,下面讓你們看一個不多人會知道的科技,感謝walterlv 提供
重寫運算返回
不多人知道實際上重寫 ==
能夠返回任意的類型,而不是隻有 bool 類型,請看下面代碼
是能夠編譯經過的,由於我重寫運算
class Foo { public int Count { get; set; } public static string operator ==(Foo f1, Foo f2) { if (f1?.Count == f2?.Count) { return "lindexi"; } return ""; } public static string operator !=(Foo f1, Foo f2) { return ""; } }
能夠重寫的運算不少,返回值能夠本身隨意定義
await 任何類型
等待任意的類型,包括已定義的基礎類型,以下面代碼
await "林德熙逗比"; await "不告訴你";
這個代碼是能夠編譯經過的,可是隻有在個人設備。在看了這個博客以後,可能你也能夠在你的設備編譯
其實 await 是能夠寫不少次的,以下面代碼
await await await await await await await await await await await await await await await await await await await await await await await "林德熙逗比";
變量名使用中文
實際上在C#支持全部 Unicode 字符,這是編譯器支持的,因此變量名使用中文也是能夠的,並且可使用特殊的字符
public string H\u00e5rføner() { return "能夠編譯"; }
if this == null
通常看到下面的代碼都以爲是不可能進入輸出的
if (this == null) Console.WriteLine("this is null");
若是在 if 裏面都能使用 this == null 成立,那麼必定是vs炸了。實際上這個代碼仍是能夠運行的
在通常的函數,以下面的 Foo 函數,在調用就須要使用f.Foo()
的方法,方法裏 this 就是 f 這個對象,若是 f == null
那麼在調用方法就直接不讓運行,如何到方法裏的判斷
f.Foo(); //若是 f 爲空,那麼這裏就不執行 void Foo() { // 若是 this 爲空,怎麼能夠調用這個方法 if (this == null) Console.WriteLine("this is null"); }
其實是能夠作的,請看(C#)if (this == null)?你在逗我,this 怎麼可能爲 null!用 IL 編譯和反編譯看穿一切 - walterlv 這篇博客
如上面博客,關鍵在修改 callvirt
爲 call
調用,直接修改 IL 能夠作出不少特殊的寫法
那麼這個能夠用在哪裏?能夠用在防止大神反編譯,如須要使用下面邏輯
//執行的代碼 //不執行的代碼
此時簡單的反編譯也許會這麼寫
if(true) { //執行的代碼 } else { //不執行的代碼 }
可是直接寫 true 很容易讓反編譯看到不使用代碼,並且在優化代碼會被去掉,因此可使用下面代碼
if(this == null) { //執行的代碼 } else { //不執行的代碼 }
實際在微軟代碼也是這樣寫,點擊string的實現源代碼能夠看到微軟代碼
重載的運算符
實際上我能夠將 null 強轉某個類,建立一個新的對象,請看代碼
Fantastic fantastic = (FantasticInfo) null; fantastic.Foo();
這裏的 FantasticInfo 和 Fantastic 沒有任何繼承關係,並且調用 Foo 不會出現空引用,也就是 fantastic 是從一個空的對象建立出來的
是否是以爲上面的科技很黑,實際原理沒有任何黑的科技,請看代碼
public class Fantastic { private Fantastic() { } public static implicit operator Fantastic(FantasticInfo value) => new Fantastic(); public void Foo() { } } public class FantasticInfo { }
經過這個方式可讓開發者沒法直接建立 Fantastic 類,並且在不知道 FantasticInfo 的狀況沒法建立 Fantastic 也就是讓你們須要瞭解 FantasticInfo 才能夠經過上面的方法建立,具體請看只有你能 new 出來!.NET 隱藏構造函數的 n 種方法(Builder Pattern / 構造器模式) - walterlv
課件連接: https://r302.cc/J4gxOX
固然還有新的 C# 7.0 和 C# 8.0 的新的語法
例以下面的內部方法返回自身
方法返回自身能夠接近無限調用
有一天我看到了下面的代碼,你猜小夥伴用什麼代碼定義了 Foo 這個代碼?
Foo()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()()();
其實只須要定義一個委託,用內部方法實現委託,由於內部方法是能夠返回自身,因而就可使用5行代碼寫出 Foo 的定義
delegate Foo Foo(); // 定義委託 static void Main(string[] args) { Foo Foo() // 定義內部方法 { return Foo; } }
不過括號還不能夠無限使用,由於編譯器有一個表達式的長度限制
無限長度的委託調用
試試這個代碼,也許你能夠無限寫下去,只要 Roslyn 不會炸就能夠
delegate Fx Fx(Fx fx); Fx fx = fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx => fx;
如下部分準確來講是 .NET 提供的功能,請問 C# 和 .NET 是什麼關係?其實我也沒法用一兩句話說清,扔掉了 .NET 依然能夠用 C# 寫程序,反過來扔掉 C# 也依然能用 .NET 寫程序
表達式樹獲取函數命名
定義一個類,下面經過表達式樹從類得到函數命名
class Foo { public void KzcSevfio() { } }
static void Main(string[] args) { GetMethodName<Foo>(foo => foo.KzcSevfio()); } private static void GetMethodName<T>(Expression<Action<T>> action) where T : class { if (action.Body is MethodCallExpression expression) { Console.WriteLine(expression.Method.Name); } }
這樣就能夠拿到函數的命名
DebuggerDisplay
若是想要在調試的時候,鼠標移動到變量顯示他的信息,能夠重寫類的 ToString
public sealed class Foo { public int Count { get; set; } public override string ToString() { return Count.ToString(); } }
可是若是 ToString 被其餘地方用了,如何顯示?
微軟告訴你們,使用 DebuggerDisplay 特性
[DebuggerDisplay("{DebuggerDisplay}")] public sealed class Foo { public int Count { get; set; } private string DebuggerDisplay => $"(count {Count})"; }
他可使用私有的屬性、字段,使用方法很簡單
參見Using the DebuggerDisplay Attribute
數字格式
string format = "000;-#;(0)"; string pos = 1.ToString(format); // 001 string neg = (-1).ToString(format); // -1 string zer = 0.ToString(format); // (0)
參見:自定義數字格式字符串
調用堆棧
若是須要得到調用方法的堆棧,可使用這個文章的方法
class Program { static void Main(string[] args) { var foo = new Foo(); foo.F1(); } } public sealed class Foo { public void F1() { F2(); } void F2() { var stackTrace = new StackTrace(); var n = stackTrace.FrameCount; for (int i = 0; i < n; i++) { Console.WriteLine(stackTrace.GetFrame(i).GetMethod().Name); } } }
輸出
F2 F1
參見:WPF 判斷調用方法堆棧
歡迎加入 dotnet 職業技術學院 https://t.me/dotnet_campus 使用 Telegram 方法請看 如何使用 Telegram
特別感謝
特別感謝 呂毅 - walterlv 提供的逗比代碼
特別感謝隊長提供的 .NET Core也是國產化信息系統開發的重要選項 - 張善友 - 博客園 博客。本文開頭爲了更準確的描述,因而抄了隊長的博客內容