且听疯吟 在此记录扯淡的青春
如果 C# 支持 void 作为泛型参数

有些问题可能看起来无所谓,但是开开脑洞还是挺有意思的

比如,最近碰到一个问题,简化形式是这样的:

IEnumerable<int> Range(int n) {
    var i = 0;
    while (i <= n) {
        // DoSomething1...
        yield return i;
        i++;
    }
}
        
Range(n).ForEach(x => DoSomething2(x));

void DoSomething2(x) {
}

看出问题了么?

  • IEnumerable 接口是没有 ForEach 方法的,ForEachList<T> 的方法,所以只能写成

    Enumerable.Range(0, n).ToList().ForEach(x => DoSomething(x));
    

    但是显然这样就失去了延迟执行的意义了

  • 我们也可以尝试使用 IEnumerableSelect 方法,变成这样:

    Enumerable.Range(0, n).Select(x => DoSomething2(x));
    

    当然这样也行不通,因为我们的 DoSomething2 方法是 void 类型的

  • 最后只能粗暴的给 DoSomething2 包装一个返回值

    Enumerable.Range(0, n).Select(x => { DoSomething2(x); return true; });
    

如果 IEnumerable 支持 ForEach 方法就好了?

  • 事实上 C# 设计者对此作了解释:“foreach” vs “ForEach”
  • 总之来说,List<T>.ForEachList<T> 本身的方法, Linq 不会提供有副作用的方法,它违反了 Linq side-effect-free 的设计理念
  • Mmm,听起来好像很有道理 :(

当然,这个例子本身不具有太大的意义,但是——

不妨开个脑洞

如果可以用 void 作为泛型参数呢?

  • 这样的代码不再有问题了
    Enumerable.Range(0, n).Select<int, void>(x => DoSomething2(x));
    
  • 也并没有违反 Linq 的设计理念(当然,取决于你怎么看了 :(
  • 更大的好处在于:

如果把 void 看作泛型的一种,可以在不增加复杂度的前提下简化一些问题
比如不需要为 MyClassMyClass<T> 写两次相同的代码了
因为 MyClass 就相当于 MyClass<void>,我们可以统一所有处理逻辑,不再需要大堆的重载了。


搜索的时候发现有很多大牛讨论过这个问题了,受益匪浅
比如 C#的设计缺陷(2):不能以void作为泛型参数

  • posted on 2015-06-26 18:54
  • C#