有一個東西叫作鴨子類型,所謂鴨子類型就是,只要一個東西表現得像鴨子那麼就能推出這玩意就是鴨子。less
C# 裏面其實也暗藏了不少相似鴨子類型的東西,可是不少開發者並不知道,所以也就無法好好利用這些東西,那麼今天我細數一下這些藏在編譯器中的細節。異步
Task
和 ValueTask
才能 await
在 C# 中編寫異步代碼的時候,咱們常常會選擇將異步代碼包含在一個 Task
或者 ValueTask
中,這樣調用者就能用 await
的方式實現異步調用。async
西卡西,並非只有 Task
和 ValueTask
才能 await
。Task
和 ValueTask
背後明明是由線程池參與調度的,但是爲何 C# 的 async
/await
卻被說成是 coroutine
呢?this
由於你所 await
的東西不必定是 Task
/ValueTask
,在 C# 中只要你的類中包含 GetAwaiter()
方法和 bool IsCompleted
屬性,而且 GetAwaiter()
返回的東西包含一個 GetResult()
方法、一個 bool IsCompleted
屬性和實現了 INotifyCompletion
,那麼這個類的對象就是能夠 await
的 。線程
所以在封裝 I/O 操做的時候,咱們能夠自行實現一個 Awaiter
,它基於底層的 epoll
/IOCP
實現,這樣當 await
的時候就不會建立出任何的線程,也不會出現任何的線程調度,而是直接讓出控制權。而 OS 在完成 I/O 調用後經過 CompletionPort
(Windows) 等通知用戶態完成異步調用,此時恢復上下文繼續執行剩餘邏輯,這其實就是一個真正的 stackless coroutine
。code
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; } }
事實上,.NET Core 中的 I/O 相關的異步 API 也的確是這麼作的,I/O 操做過程當中是不會有任何線程分配等待結果的,都是 coroutine
操做:I/O 操做開始後直接讓出控制權,直到 I/O 操做完畢。而之因此有的時候你發現 await
先後線程變了,那只是由於 Task
自己被調度了。對象
UWP 開發中所用的 IAsyncAction
/IAsyncOperation<T>
則是來自底層的封裝,和 Task
沒有任何關係可是是能夠 await
的,而且若是用 C++/WinRT 開發 UWP 的話,返回這些接口的方法也都是能夠 co_await
的。索引
IEnumerable
和 IEnumerator
才能被 foreach
常常咱們會寫以下的代碼:接口
foreach (var i in list) { // ...... }
而後一問爲何能夠 foreach
,大多都會回覆由於這個 list
實現了 IEnumerable
或者 IEnumerator
。開發
可是實際上,若是想要一個對象可被 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()
變爲 GetAsyncEnumerator()
和 MoveNextAsync()
。
其中 MoveNextAsync()
返回的東西應該是一個 Awaitable<bool>
,至於這個 Awaitable
究竟是什麼,它能夠是 Task
/ValueTask
,也能夠是其餘的或者你本身實現的。
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
要怎麼實現 IDisposable
衆所周知 ref struct
由於必須在棧上且不能被裝箱,因此不能實現接口,可是若是你的 ref struct
中有一個 void Dispose()
那麼就能夠用 using
語法實現對象的自動銷燬。
ref struct MyDisposable { public void Dispose() => throw new NotImplementedException(); } class Program { public static void Main() { using var y = new MyDisposable(); // ...... } }
Range
才能使用切片C# 8 引入了 Ranges,容許切片操做,可是其實並非必須提供一個接收 Range
類型參數的 indexer 才能使用該特性。
只要你的類能夠被計數(擁有 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
索引倒數第一個元素,可是其實並非必須提供一個接收 Index
類型參數的 indexer 才能使用該特性。
只要你的類能夠被計數(擁有 Length
或 Count
屬性),而且能夠被索引(擁有一個接收 int
參數的索引器),那麼就能夠用該特性。
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]; } }
如何給一個類型實現解構呢?其實只須要寫一個名字爲 Deconstruct()
的方法,而且參數都是 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; } }