Func<> 能取代 delegate 吗?
今天写程序,忽然想起来很久之前在知乎上看到的一个提问:
c#中的delegate 是不是完全被 func 和action 替代了?
这门语言直接删掉delegate会不会更方便?
众所周知,Func<in TIn, out TOut>的就是一个泛型委托,通过不同输入参数数量的重载定义,Func<>能取代绝大部分delegate。
当时我对这个问题我对这个问题并没有什么感受:毕竟Func<>是用委托定义的,删掉delegate属实是无稽之谈;但另一方面,提问者关于Func<>的感受确实是实实在在真实的,Func<>的的确确省掉了绝大部分需要手工定义委托的场景。
但今天我在写程序的过程中,发现一个有意思的需求,也不能用Func<>来表达。考虑我们有一个函数 GetOrProcess(input, mapInput),它每次会根据input优先从缓存中搜寻结果,如果没有,就使用mapInput(input)计算结果。很自然地,我们可以写下这段方法:
public T GetOrProcess(ReadOnlySpan<int> input, Func<ReadOnlySpan<int>,T> mapInput)
{
// ... 计算hash并查找缓存(省略实现细节)
// 假设未命中缓存
var result = mapInput(input);
return result;
}但是以上的代码并不能编译:
The type 'ReadOnlySpan' may not be a ref struct or a type parameter allowing ref structs in order to use it as parameter 'T' in the generic type or method 'Func<T, TResult>'
原因是编译器要求 ref struct 不能被被作为泛型参数送入泛型类型或者泛型方法 。
让我们回顾Func<>的定义:
public delegate TOut Func<in TIn, out TOut>(TIn arg);从本质上说,Func<TIn, TOut>是一个泛型委托。尤其是这里的TIn,会在后续我们的代码中被填充成ReadOnlySpan<int>,这一步违反了编译器说不能把ref struct作为泛型参数的要求。
解决办法
既然不让把ReadOnlySpan<int>作为泛型参数,我们直接使用具体的类型:
public delegate
T MapInput(ReadOnlySpan<int> input);你可能会争论:这里的T不还是一个泛型吗?没错。但是这个泛型委托的输入参数变成了具体类型ReadOnlySpan<int>!然后,我们可以直接使用这里的手工定义的委托取代之前的Func<>:
public T GetOrProcess(ReadOnlySpan<int> input, MapInput mapInput)
{
// ... 计算hash并查找缓存(省略实现细节)
// 假设未命中缓存
var result = mapInput(input);
return result;
}