日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > C# >内容正文

C#

C#中的9个“黑魔法”与“骚操作”

發布時間:2023/12/4 C# 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C#中的9个“黑魔法”与“骚操作” 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

C#中的9個“黑魔法”與“騷操作”

我們知道?C#是非常先進的語言,因為是它很有遠見的“語法糖”。這些“語法糖”有時過于好用,導致有人覺得它是?C#編譯器寫死的東西,沒有道理可講的——有點像“黑魔法”。

那么我們可以看看?C#這些高級語言功能,是編譯器寫死的東西(“黑魔法”),還是可以擴展(騷操作)的“鴨子類型”。

我先列一個目錄,大家可以對著這個目錄試著下判斷,說說是“黑魔法”(編譯器寫死),還是“鴨子類型”(可以自定義“騷操作”):

  • LINQ操作,與?IEnumerable<T>類型;

  • async/await,與?Task/?ValueTask類型;

  • 表達式樹,與?Expression<T>類型;

  • 插值字符串,與?FormattableString類型;

  • yieldreturn,與?IEnumerable<T>類型;

  • foreach循環,與?IEnumerable<T>類型;

  • using關鍵字,與?IDisposable接口;

  • T?,與?Nullable<T>類型;

  • 任意類型的?Index/Range泛型操作。

  • 1.?LINQ操作,與?IEnumerable<T>類型

    不是“黑魔法”,是“鴨子類型”。

    LINQ是?C# 3.0發布的新功能,可以非常便利地操作數據。現在?12年過去了,雖然有些功能有待增強,但相比其它語言還是方便許多。

    如我上一篇博客提到,?LINQ不一定要基于?IEnumerable<T>,只需定定義一個類型,實現所需要的?LINQ表達式即可,?LINQ的?select關鍵字,會調用?.Select方法,可以用如下的“騷操作”,實現“移花接木”的效果:

    void Main() {var query = from i in new F()select 3;Console.WriteLine(string.Join(",", query)); // 0,1,2,3,4 } class F {public IEnumerable<int> Select<R>(Func<int, R> t){for (var i = 0; i < 5; ++i){yield return i;}} }

    2.?async/await,與?Task/?ValueTask類型

    不是“黑魔法”,是“鴨子類型”。

    async/await發布于?C# 5.0,可以非常便利地做異步編程,其本質是狀態機。

    async/await的本質是會尋找類型下一個名字叫?GetAwaiter()的接口,該接口必須返回一個繼承于?INotifyCompletion或?ICriticalNotifyCompletion的類,該類還需要實現?GetResult()方法和?IsComplete屬性。

    這一點在?C#語言規范中有說明,調用?awaitt本質會按如下順序執行:

  • 先調用?t.GetAwaiter()方法,取得等待器?a;

  • 調用?a.IsCompleted取得布爾類型?b;

  • 如果?b=true,則立即執行?a.GetResult(),取得運行結果;

  • 如果?b=false,則看情況:

  • 如果?a沒實現?ICriticalNotifyCompletion,則執行?(aasINotifyCompletion).OnCompleted(action)

  • 如果?a實現了?ICriticalNotifyCompletion,則執行?(aasICriticalNotifyCompletion).OnCompleted(action)

  • 執行隨后暫停,?OnCompleted完成后重新回到狀態機;

  • 有興趣的可以訪問?Github具體規范說明:https://github.com/dotnet/csharplang/blob/master/spec/expressions.md#runtime-evaluation-of-await-expressions

    正常?Task.Delay()是基于?線程池計時器的,可以用如下“騷操作”,來實現一個單線程的?TaskEx.Delay():

    static Action Tick = null; void Main() {Start();while (true){if (Tick != null) Tick();Thread.Sleep(1);} } async void Start() {Console.WriteLine("執行開始");for (int i = 1; i <= 4; ++i){Console.WriteLine($"第{i}次,時間:{DateTime.Now.ToString("HH:mm:ss")} - 線程號:{Thread.CurrentThread.ManagedThreadId}");await TaskEx.Delay(1000);}Console.WriteLine("執行完成"); } class TaskEx {public static MyDelay Delay(int ms) => new MyDelay(ms); } class MyDelay : INotifyCompletion {private readonly double _start;private readonly int _ms;public MyDelay(int ms){_start = Util.ElapsedTime.TotalMilliseconds;_ms = ms;}internal MyDelay GetAwaiter() => this;public void OnCompleted(Action continuation){Tick += Check;void Check(){if (Util.ElapsedTime.TotalMilliseconds - _start > _ms){continuation();Tick -= Check;}}}public void GetResult() {}public bool IsCompleted => false; }

    運行效果如下:

    執行開始 第1次,時間:17:38:03 - 線程號:1 第2次,時間:17:38:04 - 線程號:1 第3次,時間:17:38:05 - 線程號:1 第4次,時間:17:38:06 - 線程號:1 執行完成

    注意不需要非得使用?TaskCompletionSource<T>才能創建定定義的?async/await。

    3. 表達式樹,與?Expression<T>類型

    是“黑魔法”,沒有“操作空間”,只有當類型是?Expression<T>時,才會創建為表達式樹。

    表達式樹是?C# 3.0隨著?LINQ一起發布,是有遠見的“黑魔法”。

    如以下代碼:

    Expression<Func<int>> g3 = () => 3;

    會被編譯器翻譯為:

    Expression<Func<int>> g3 = Expression.Lambda<Func<int>>(Expression.Constant(3, typeof(int)), Array.Empty<ParameterExpression>());

    4. 插值字符串,與?FormattableString類型

    是“黑魔法”,沒有“操作空間”。

    插值字符串發布于?C# 6.0,在此之前許多語言都提供了類似的功能。

    只有當類型是?FormattableString,才會產生不一樣的編譯結果,如以下代碼:

    FormattableString x1 = $"Hello {42}"; string x2 = $"Hello {42}";

    編譯器生成結果如下:

    FormattableString x1 = FormattableStringFactory.Create("Hello {0}", 42); string x2 = string.Format("Hello {0}", 42);

    注意其本質是調用了?FormattableStringFactory.Create來創建一個類型。

    5.?yieldreturn,與?IEnumerable<T>類型;

    是“黑魔法”,但有補充說明。

    yieldreturn除了用于?IEnumerable<T>以外,還可以用于?IEnumerable、?IEnumerator<T>、?IEnumerator。

    因此,如果想用?C#來模擬?C++/?Java的?generator<T>的行為,會比較簡單:

    var seq = GetNumbers(); seq.MoveNext(); Console.WriteLine(seq.Current); // 0 seq.MoveNext(); Console.WriteLine(seq.Current); // 1 seq.MoveNext(); Console.WriteLine(seq.Current); // 2 seq.MoveNext(); Console.WriteLine(seq.Current); // 3 seq.MoveNext(); Console.WriteLine(seq.Current); // 4 IEnumerator<int> GetNumbers() {for (var i = 0; i < 5; ++i)yield return i; }

    yieldreturn——“迭代器”發布于?C# 2.0。

    6.?foreach循環,與?IEnumerable<T>類型

    是“鴨子類型”,有“操作空間”。

    foreach不一定非要配合使用?IEnumerable<T>類型,只要對象存在?GetEnumerator()方法即可:

    void Main() {foreach (var i in new F()){Console.Write(i + ", "); // 1, 2, 3, 4, 5,} } class F {public IEnumerator<int> GetEnumerator(){for (var i = 0; i < 5; ++i){yield return i;}} }

    另外,如果對象實現了?GetAsyncEnumerator(),甚至也可以一樣使用?awaitforeach異步循環:

    async Task Main() {await foreach (var i in new F()){Console.Write(i + ", "); // 1, 2, 3, 4, 5,} } class F {public async IAsyncEnumerator<int> GetAsyncEnumerator(){for (var i = 0; i < 5; ++i){await Task.Delay(1);yield return i;}} }

    awaitforeach是?C# 8.0隨著?異步流一起發布的,具體可見我之前寫的《代碼演示C#各版本新功能》。

    7.?using關鍵字,與?IDisposable接口

    是,也不是。

    引用類型和正常的?值類型用?using關鍵字,必須基于?IDisposable接口。

    但?refstruct和?IAsyncDisposable就是另一個故事了,由于?refstruct不允許隨便移動,而引用類型——托管堆,會允許內存移動,所以?refstruct不允許和?引用類型產生任何關系,這個關系就包含繼承?接口——因為?接口也是?引用類型。

    但釋放資源的需求依然存在,怎么辦,“鴨子類型”來了,可以手寫一個?Dispose()方法,不需要繼承任何接口:

    void S1Demo() {using S1 s1 = new S1(); } ref struct S1 {public void Dispose(){Console.WriteLine("正常釋放");} }

    同樣的道理,如果用?IAsyncDisposable接口:

    async Task S2Demo() {await using S2 s2 = new S2(); } struct S2 : IAsyncDisposable {public async ValueTask DisposeAsync(){await Task.Delay(1);Console.WriteLine("Async釋放");} }

    8.?T?,與?Nullable<T>類型

    是“黑魔法”,只有?Nullable<T>才能接受?T?,?Nullable<T>作為一個?值類型,它還能直接接受?null值(正常?值類型不允許接受?null值)。

    示例代碼如下:

    int? t1 = null; Nullable<int> t2 = null; int t3 = null; // Error CS0037: Cannot convert null to 'int' because it is a non-nullable value type

    生成代碼如下(?int?與?Nullable<int>完全一樣,跳過了編譯失敗的代碼):

    IL_0000: nop IL_0001: ldloca.s 0 IL_0003: initobj valuetype [System.Runtime]System.Nullable`1<int32> IL_0009: ldloca.s 1 IL_000b: initobj valuetype [System.Runtime]System.Nullable`1<int32> IL_0011: ret

    9. 任意類型的?Index/Range泛型操作

    有“黑魔法”,也有“鴨子類型”——存在操作空間。

    Index/Range發布于?C# 8.0,可以像?Python那樣方便地操作索引位置、取出對應值。以前需要調用?Substring等復雜操作的,現在非常簡單。

    string url = "https://www.super-cool.com/product/7705a33a-4d2c-455d-a42c-c95e6ac8ee99/summary"; string productId = url[35..url.LastIndexOf("/")]; Console.WriteLine(productId);

    生成代碼如下:

    string url = "https://www.super-cool.com/product/7705a33a-4d2c-455d-a42c-c95e6ac8ee99/amd-r7-3800x"; int num = 35; int length = url.LastIndexOf("/") - num; string productId = url.Substring(num, length); Console.WriteLine(productId); // 7705a33a-4d2c-455d-a42c-c95e6ac8ee99

    可見,?C#編譯器忽略了?Index/Range,直接翻譯為調用?Substring了。

    但數組又不同:

    var range = new[] { 1, 2, 3, 4, 5 }[1..3]; Console.WriteLine(string.Join(", ", range)); // 2, 3

    生成代碼如下:

    int[] range = RuntimeHelpers.GetSubArray<int>(new int[5] {1,2,3,4,5 }, new Range(1, 3)); Console.WriteLine(string.Join<int>(", ", range));

    可見它確實創建了?Range類型,然后調用了?RuntimeHelpers.GetSubArray<int>,完全屬于“黑魔法”。

    但它同時也是“鴨子”類型,只要代碼中實現了?Length屬性和?Slice(int,int)方法,即可調用?Index/Range:

    var range2 = new F()[2..]; Console.WriteLine(range2); // 2 -> -2 class F {public int Length { get; set; }public IEnumerable<int> Slice(int start, int end){yield return start;yield return end;} }

    生成代碼如下:

    F f = new F(); int length2 = f.Length; length = 2; num = length2 - length; string range2 = f.Slice(length, num); Console.WriteLine(range2);

    總結

    如上所見,?C#的“黑魔法”確實挺多,但“鴨子類型”也有很多,“騷操作”的“操作空間”很大。

    據傳?C# 9.0將添加“鴨子類型”的元祖——?TypeClasses,到時候“操作空間”肯定比現在更大,非常期待!

    喜歡的朋友請關注我的微信公眾號:【DotNet騷操作】

    總結

    以上是生活随笔為你收集整理的C#中的9个“黑魔法”与“骚操作”的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。