考虑以下示例:
class Test
{
public void Fun<T>(Func<T, T> f)
{
}
public string Fun2(string test)
{
return "";
}
public Test()
{
Fun<string>(Fun2);
}
}
编译好。
我想知道为什么我不能删除<string>
泛型参数?我收到一个错误,无法从使用中推断出来。
我知道这样的推断可能对编译器有挑战性,但似乎有可能。
我想解释一下这种行为。
编辑回答Jon Hanna的回答:
那为什么会这样呢?
class Test
{
public void Fun<T1, T2>(T1 a, Func<T1, T2> f)
{
}
public string Fun2(int test)
{
return test.ToString();
}
public Test()
{
Fun(0, Fun2);
}
}
此处我只使用T1 a
绑定一个参数,但T2
似乎同样困难。
答案 0 :(得分:13)
它不能推断出类型,因为这里没有定义类型。
Fun2
不是Func<string, string>
,可以将某些内容分配给Func<string, string>
。
所以如果你使用:
public Test()
{
Func<string, string> del = Fun2;
Fun(del);
}
或者:
public Test()
{
Fun((Func<string, string>)Fun2);
}
然后,您明确地从Func<string, string>
创建Fun2
,并且通用类型推断也会相应地工作。
相反,当你这样做时:
public Test()
{
Fun<string>(Fun2);
}
然后,重载集Fun<string>
只包含一个接受Func<string, string>
的重载,编译器可以推断出你想要使用Fun2
。
但是你要求它根据参数的类型推断泛型类型,以及基于泛型类型的参数类型。这比其中任何一种推理类型都要大。
(值得考虑的是,在.NET 1.0中,委托不仅不是通用的 - 因此您必须定义delgate string MyDelegate(string test)
- 但是还必须使用构造函数Fun(new MyDelegate(Fun2))
创建对象。语法已经改变,可以通过多种方式更轻松地使用委托,但隐式使用Fun2
作为Func<string, string>
仍然构成了幕后的委托对象。
那为什么会这样呢?
class Test
{
public void Fun<T1, T2>(T1 a, Func<T1, T2> f)
{
}
public string Fun2(int test)
{
return test.ToString();
}
public Test()
{
Fun(0, Fun2);
}
}
因为它可以按顺序推断:
T1
是int
。Fun2
被分配给某些Func<int, T2>
的{{1}}。T2
为Fun2
,则可以将Func<int, T2>
分配给T2
。因此string
是字符串。特别是,一旦有了参数类型,就可以从函数中推断出T2
的返回类型。这也是一样的(并且值得编译器付出努力),因为它在Linq的Func
中很重要。这带来了一个相关的案例,事实是只有Select
我们没有足够的信息来知道lambda被强制转换为什么。一旦我们知道x.Select(i => i.ToString())
是x
还是IEnumerable<T>
我们知道我们是IQueryable<T>
还是Func<T, ?>
,其余的都可以从那里推断出来。
这里也值得注意的是,推断返回类型并不会导致推断其他类型的歧义。考虑我们是否同时拥有Expression<Func<T, ?>>
(采用Fun2
的那个和采用string
的那个)。这是有效的C#重载,但扣除了int
Func<T, string>
的类型,Fun2
可以被转换为不可能;两者都有效。
但是,虽然.NET确实允许在返回类型上重载,但C#没有。因此,一旦确定了Func<T, TResult>
的类型,就没有有效的C#程序对从方法(或lambda)创建的T
的返回类型不明确。相对容易,再加上非常有用,使得编译器可以为我们推断它。
答案 1 :(得分:3)
您希望编译器从string
推断Fun2
,这对C#编译器的要求太高了。这是因为在Fun2
中引用时,它会将Test
视为方法组,而不是委托。
如果您更改代码以传递Fun2
所需的参数并从Fun
调用它,则需要消失,因为您现在拥有string
参数,允许要推断的类型:
class Test
{
public void Fun<T>(Func<T, T> f, T x)
{
f(x);
}
public string Fun2(string test)
{
return test;
}
public Test()
{
Fun(Fun2, "");
}
}
要回答您编辑的问题版本,请提供您现在提供编译器的T1
类型 - 通过Fun(0, Fun2);
调用 - 附加信息。它现在知道它需要一个来自Fun2
方法组的方法,该方法具有T1
参数,在这种情况下为int
。这将其缩小为一种方法,因此可以推断出使用哪种方法。