C#黑魔法:鸭子类型(Duck Typing)
如果它走起路来像鸭子,叫起来像鸭子,那么它就是鸭子。
鸭子类型,主要应用于动态语言类型,比如JS、Python等,核心理念为:关注对象的行为(方法或属性)而非其具体类型。只要对象具备所需行为,即可在特定场景中使用,无需显式继承或实现接口。
来,在C#中使用鸭子类型
魔法:
await 不必是 Task和ValueTask
对象
TPL是官推荐的C#异步编程模型,几乎所有提到TPL异步编程时,都必须是async配合await,等待一个Task或ValueTask.
事实上,并不是只有 Task 和 ValueTask 才能 await:只要符合下列条件的类,都能await
- 类中包含 GetAwaiter() 实例方法:返回一个实现了 INotifyCompletion 接口的 awaiter 对象
- 类中包含 bool类型的 IsCompleted 属性:用于告知 awaiter 是否已经完成了其操作
- 类中包含一个 OnCompleted 方法:
说个秘密:.NET Core 中的 I/O 相关的异步 API 也的确是这么做的,I/O 操作过程中是不会有任何线程分配等待结果的,都是 coroutine 操作:I/O 操作开始后直接让出控制权,直到 I/O 操作完毕。
而之所以有的时候你发现 await 前后线程变了,那只是因为 Task 本身被调度了。
public class CustomTask<T>
{
public CustomAwaiter<T> GetAwaiter()
{
return new CustomAwaiter<T>();
}
}
public class CustomAwaiter<T> : System.Runtime.CompilerServices.INotifyCompletion
{
public bool IsCompleted { get; private set; }
public T GetResult()
{
Console.WriteLine("获取异步结果");
return default(T);
}
public void OnCompleted(Action continuation)
{
Console.WriteLine("注册异步完成回调");
IsCompleted = true;
continuation?.Invoke();
}
}
var obj = new CustomTask<int>();
var r = await obj;
r.Display();
foreach 不必是 IEnumerable 和 IEnumerator
对象
满足以下条件的对象,就能使用 foreach:
- 类中只要有 GetEnumerator() 方法即可;
- GetEnumerator() 返回的对象包含一个 bool MoveNext() 方法加一个 Current 属性
//作为 GetEnumerator 方法的返回类
public class CustomEnumerator<T>
{
public T Current { get; private set; }
public bool MoveNext()
{
//这里写业务逻辑
return false;
}
}
//只要有GetEnumerator方法,且返回值符合要求,就行了。
public class CustomEnumerable<T>
{
public CustomEnumerator<T> GetEnumerator()
{
return new CustomEnumerator<T>();
}
}
//使用 foreach 查询
var names = new CustomEnumerable<string>();
foreach(var name in names)
{
Console.WriteLine(name);
}
LINQ 不必是 IEnumerable对象
常见的Linq表达式语法:
var result = from q in source
where q.StartsWith("s")
select q;
代码中的 source 的类型不一定非要实现 IEnumerable 接口。
事实上,只要有对应名字的方法就可以了。比如:有了名为 Select 的方法就能用 select,有了名为 Where 的方法就能用 where
public class Custom<TSource>
{
private readonly TSource value;
public Custom(TSource value) { this.value = value; }
public Custom<TResult> Select<TResult>(Func<TSource, TResult> selector)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
if (selector == null)
{
throw new ArgumentNullException(nameof(selector));
}
return new Custom<TResult>(selector(value));
}
public Custom<TSource> Where(Func<TSource, bool> predicate)
{
var r = predicate(value);
if (r)
{
return this;
}
else
{
return null;
}
}
public new string ToString() => $"自定义Linq类: {value}";
}
上面 Custom 类,有了 Select 和 Where 方法,就可以使用 linq表达式 select 和 where
//声明对象
var source = new Custom<string>("select");
//使用linq表达式查询
var qResult = from m in source
where m.StartsWith("s")
select new { Name=source.ToString(), Age=1 };
//结果(转化成一个匿名类)
Console.WriteLine(qResult.ToString());
using 对象, 不必实现 IDisposable接口
ref struct 因为必须在栈上且不能被装箱,所以不能实现接口。
只要 ref struct 对象中有一个 void Dispose() 方法,那么就可以用 using 语法实现对象的自动销毁。
//声明带 void Dispose()方法的引用类struct
ref struct MyRefStruct
{
public string ToLower(string source)
{
return source.ToLower();
}
public void Dispose()
{
//清理业务
}
}
//使用using语句,实现自动销毁
using (var myRef = new MyRefStruct())
{
Console.WriteLine(myRef.ToLower("ABCEDF"));
}
普通类也能解构(非解析)
给一个普通类实现解构:只需要有一个名字为 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;
}
}
//实现解析操作
var x = new MyDeconstruct();
var (o, u) = x;
Console.WriteLine($"解构后,o={o},u={u}");