C# 当中 Lambda 函数的匿名类型推断

举个栗子:

delegate T MyFunc<T>();

static void WriteResult<T>(MyFunc<T> function)
{
    Console.WritleLine(function());
}

...

WriteResult(delegate { return 5; });

在C#2.0当中以上代码会报错,因为C#2的类型推断是单独针对每一个实参来进行的,从一个实参推断出的类型无法直接用于另外一个实参。在本例当中,function的输入值是T,返回值也是T,C#2.0的简单规则似乎无法为我们解决问题。
这个时候有两种解决办法:

WriteResult<int>(delegate { return 5; }); // 显示指定类型实参
WriteResult((MyFunc<int>)delegate { return 5; }); // 将匿名委托强制转换为具体的委托类型,而不是泛型委托

而C#3则做了更为精致的工作,他能够像处理隐式类型数组一样来处理匿名委托的返回类型。

delegate T MyFunc<T>();

static void WriteResult<T>(MyFunc<T> function)
{
   Console.WriteLine(function());
}

WriteResult(delegate 
{
   if(DateTime.Now.Hour < 12)
   {
       return 10;
   }else
   {
       return new object();
   }
});

这个时候编译器会构建一个集合,包含了匿名委托当中return 当中的所有返回类型,并且检查这个集合当中所有的类型都能够隐式转换为其中一个类型。在上面的例子当中,则是int和object,int->object通过装箱会存在一个隐式类型转换,而object到int则不可能,所以Object被推断为这个匿名委托的返回类型。

辣么,我们下一个例子更加复杂的解释了两个非固定的变量如何被成功推断出来的:

static void PrintConvertedValue<TInput,TOutput>(TInput input,Converter<TInput,TOutput> converter)
{
   Console.WriteLine(convertrt(input));
}
...
PrintConverter("I am a string.",x=>x.Length);

以上代码需要经过一下几步推断:
1.第一个参数是TInput类型,实参传递的是string类型,辣么我们可以推断出TOutput肯定存在到TInput的转换。
2.第二个参数是Converter<TInput,TOutput>类型,第二个实参则是一个饮食类型的Lambda表达式,此时不进行任何推断。
3.TInput不依赖任何非固定类型参数,所以被确定为string类型。
4.第二个实参现在有一个固定的输入类型,但是有一个非固定的输出类型,即(string x)=>x.Length,并推断出其返回类型为int,因此int到TOutput必定会发生一个饮食类型转换。
直到没有推断出非固定类型为止。

辣么我们再来看一个更加复杂的例子:

static void ConvertTwice<TInput,TMiddle,TOutput>(TInput input,Converter<TInput,TMiddle> firstConversion,Converter<TMiddle,TOutput> secondConversion)
{
   TMiddle middle = firstConverstion(input);
   TOutput output = secondConversion(middle);
   Console.WriteLine(output);
}
...
ConvertTwice("Another string",text=>text.Length,lenght=>Math.Sqrt(lenght));

乍看一下十分复杂,其实很简单,我们这个函数获取一个字符串,并计算出它的长度,将这个长度进行求平方根操作。
无非都是根据string这个固定变量逐步推断出TMiddle与TOutput的类型。