有一个东西叫作鸭子类型,所谓鸭子类型就是,只要一个东西表现得像鸭子那么就能推出这玩意就是鸭子。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; } }