前言

          有一個東西叫做鴨子類型,所謂鴨子類型就是,只要一個東西表現(xiàn)得像鴨子那么就能推出這玩意就是鴨子。

          C# 里面其實(shí)也暗藏了很多類似鴨子類型的東西,但是很多開發(fā)者并不知道,因此也就沒法好好利用這些東西,那么今天我細(xì)數(shù)一下這些藏在編譯器中的細(xì)節(jié)。

          不是只有 Task 和 ValueTask 才能 await

          在 C# 中編寫異步代碼的時候,我們經(jīng)常會選擇將異步代碼包含在一個 Task 或者 ValueTask 中,這樣調(diào)用者就能用 await 的方式實(shí)現(xiàn)異步調(diào)用。

          西卡西,并不是只有 Task 和 ValueTask 才能 await。Task 和 ValueTask 背后明明是由線程池參與調(diào)度的,可是為什么 C# 的
          async/await 卻被說成是 coroutine 呢?

          因?yàn)槟闼?await 的東西不一定是 Task/ValueTask,在 C# 中只要你的類中包含 GetAwaiter() 方法和 bool
          IsCompleted 屬性,并且 GetAwaiter() 返回的東西包含一個 GetResult() 方法、一個 bool IsCompleted
          屬性和實(shí)現(xiàn)了INotifyCompletion,那么這個類的對象就是可以 await 的 。

          因此在封裝 I/O 操作的時候,我們可以自行實(shí)現(xiàn)一個 Awaiter,它基于底層的 epoll/IOCP 實(shí)現(xiàn),這樣當(dāng) await
          的時候就不會創(chuàng)建出任何的線程,也不會出現(xiàn)任何的線程調(diào)度,而是直接讓出控制權(quán)。而 OS 在完成 I/O 調(diào)用后通過CompletionPort
          (Windows) 等通知用戶態(tài)完成異步調(diào)用,此時恢復(fù)上下文繼續(xù)執(zhí)行剩余邏輯,這其實(shí)就是一個真正的stackless coroutine。
          public class MyTask<T> { public MyAwaiter<T> GetAwaiter() { return new
          MyAwaiter<T>(); } } public class MyAwaiter<T> : INotifyCompletion { public bool
          IsCompleted { get; private set; } public T GetResult() { throw new
          NotImplementedException(); } public void OnCompleted(Action continuation) {
          throw new NotImplementedException(); } } public class Program { static async
          Task Main(string[] args) { var obj = new MyTask<int>(); await obj; } }
          事實(shí)上,.NET Core 中的 I/O 相關(guān)的異步 API 也的確是這么做的,I/O 操作過程中是不會有任何線程分配等待結(jié)果的,都是 coroutine
          操作:I/O 操作開始后直接讓出控制權(quán),直到 I/O 操作完畢。而之所以有的時候你發(fā)現(xiàn)await 前后線程變了,那只是因?yàn)?Task 本身被調(diào)度了。

          UWP 開發(fā)中所用的 IAsyncAction/IAsyncOperation<T> 則是來自底層的封裝,和 Task 沒有任何關(guān)系但是是可以 await
          的,并且如果用 C++/WinRT 開發(fā) UWP 的話,返回這些接口的方法也都是可以co_await 的。

          不是只有 IEnumerable 和 IEnumerator 才能被 foreach

          經(jīng)常我們會寫如下的代碼:
          foreach (var i in list) { // ...... }
          然后一問為什么可以 foreach,大多都會回復(fù)因?yàn)檫@個 list 實(shí)現(xiàn)了 IEnumerable 或者 IEnumerator。

          但是實(shí)際上,如果想要一個對象可被 foreach,只需要提供一個 GetEnumerator() 方法,并且 GetEnumerator()
          返回的對象包含一個bool MoveNext() 方法加一個 Current 屬性即可。
          class MyEnumerator<T> { public T Current { get; private set; } public bool
          MoveNext() { throw new NotImplementedException(); } } class MyEnumerable<T> {
          public MyEnumerator<T> GetEnumerator() { throw new NotImplementedException(); }
          } class Program { public static void Main() { var x = new MyEnumerable<int>();
          foreach (var i in x) { // ...... } } }
          不是只有 IAsyncEnumerable 和 IAsyncEnumerator 才能被 await foreach

          同上,但是這一次要求變了,GetEnumerator() 和 MoveNext() 變?yōu)?GetAsyncEnumerator() 和
          MoveNextAsync()。

          其中 MoveNextAsync() 返回的東西應(yīng)該是一個 Awaitable<bool>,至于這個 Awaitable 到底是什么,它可以是 Task/
          ValueTask,也可以是其他的或者你自己實(shí)現(xiàn)的。
          class MyAsyncEnumerator<T> { public T Current { get; private set; } public
          MyTask<bool> MoveNextAsync() { throw new NotImplementedException(); } } class
          MyAsyncEnumerable<T> { public MyAsyncEnumerator<T> GetAsyncEnumerator() { throw
          new NotImplementedException(); } } class Program { public static async Task
          Main() { var x = new MyAsyncEnumerable<int>(); await foreach (var i in x) { //
          ...... } } }
          ref struct 要怎么實(shí)現(xiàn) IDisposable

          眾所周知 ref struct 因?yàn)楸仨氃跅I锨也荒鼙谎b箱,所以不能實(shí)現(xiàn)接口,但是如果你的 ref struct 中有一個 void Dispose()
          那么就可以用using 語法實(shí)現(xiàn)對象的自動銷毀。
          ref struct MyDisposable { public void Dispose() => throw new
          NotImplementedException(); } class Program { public static void Main() { using
          var y = new MyDisposable(); // ...... } }
          不是只有 Range 才能使用切片

          C# 8 引入了 Ranges,允許切片操作,但是其實(shí)并不是必須提供一個接收 Range 類型參數(shù)的 indexer 才能使用該特性。

          只要你的類可以被計數(shù)(擁有 Length 或 Count 屬性),并且可以被切片(擁有一個 Slice(int, int) 方法),那么就可以用該特性。
          class MyRange { public int Count { get; private set; } public object Slice(int
          x, int y) => throw new NotImplementedException(); } class Program { public
          static void Main() { var x = new MyRange(); var y = x[1..]; } }
          不是只有 Index 才能使用索引

          C# 8 引入了 Indexes 用于索引,例如使用 ^1 索引倒數(shù)第一個元素,但是其實(shí)并不是必須提供一個接收 Index 類型參數(shù)的 indexer
          才能使用該特性。

          只要你的類可以被計數(shù)(擁有 Length 或 Count 屬性),并且可以被索引(擁有一個接收 int 參數(shù)的索引器),那么就可以用該特性。
          class MyIndex { public int Count { get; private set; } public object this[int
          index] { get => throw new NotImplementedException(); } } class Program { public
          static void Main() { var x = new MyIndex(); var y = x[^1]; } }
          給類型實(shí)現(xiàn)解構(gòu)

          如何給一個類型實(shí)現(xiàn)解構(gòu)呢?其實(shí)只需要寫一個名字為 Deconstruct() 的方法,并且參數(shù)都是 out 的即可。
          class MyDeconstruct { private int A => 1; private int B => 2; public void
          Deconstruct(out int a, out int b) { a = A; b = B; } } class Program { public
          static void Main() { var x = new MyDeconstruct(); var (o, u) = x; } }

          友情鏈接
          ioDraw流程圖
          API參考文檔
          OK工具箱
          云服務(wù)器優(yōu)惠
          阿里云優(yōu)惠券
          騰訊云優(yōu)惠券
          京東云優(yōu)惠券
          站點(diǎn)信息
          問題反饋
          郵箱:[email protected]
          QQ群:637538335
          關(guān)注微信

                天天日天天射试看二分钟 | 国产精品午夜成人免费 | 扒开双腿疯狂进出爽爽爽网站 | 天天插天天日天天草 | 黄色视频免费观看入口 |