使用委托的"两级"泛型方法参数推断

Pio*_*fer 17 c# generics type-inference

请考虑以下示例:

class Test
{
    public void Fun<T>(Func<T, T> f)
    {
    }

    public string Fun2(string test)
    {
        return ""; 
    }

    public Test()
    {
        Fun<string>(Fun2);
    }
}
Run Code Online (Sandbox Code Playgroud)

编译好.

我想知道为什么我不能删除<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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里我只绑定一个参数T1 a,但T2似乎同样困难.

Jon*_*nna 13

它无法推断类型,因为此处未定义类型.

Fun2不是Func<string, string>,它是可以分配的东西Func<string, string>.

所以如果你使用:

public Test()
{
  Func<string, string> del = Fun2;
  Fun(del);
}
Run Code Online (Sandbox Code Playgroud)

要么:

public Test()
{
  Fun((Func<string, string>)Fun2);
}
Run Code Online (Sandbox Code Playgroud)

然后,您明确地创建了一个Func<string, string>from Fun2,并且泛型类型推断也相应地起作用.

相反,当你这样做时:

public Test()
{
  Fun<string>(Fun2);
}
Run Code Online (Sandbox Code Playgroud)

然后,重载集Fun<string>只包含一个接受a Func<string, string>的编译器,编译器可以推断出你想要使用Fun2它.

但是你要求它根据参数的类型推断泛型类型,以及基于泛型类型的参数类型.这比其中任何一种类型的推断都要大.

(值得考虑的是,在.NET 1.0中,委托不仅不是通用的 - 所以你必须定义delgate string MyDelegate(string test)- 但是也必须使用构造函数创建对象Fun(new MyDelegate(Fun2)).语法已经改变,以便在几个方面更容易地使用委托,但隐含使用Fun2as 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

因为它可以推断,按顺序:

  1. T1int.
  2. Fun2被分配给Func<int, T2>一些人T2.
  3. Fun2可以分配给一Func<int, T2>如果T2string.因此T2是字符串.

特别是,Func一旦有了参数类型,就可以从函数中推断出返回类型.这也是一样的(并且值得编译器付出努力),因为它在Linq中很重要Select.这带来了一个相关的案例,事实上x.Select(i => i.ToString())我们没有足够的信息来知道lambda被强制转换为什么.一旦我们知道xIEnumerable<T>IQueryable<T>我们知道我们是否拥有Func<T, ?>Expression<Func<T, ?>>其余的可以从那里推断出来.

这里也值得注意的是,推断返回类型并不会导致推断其他类型的歧义.考虑一下我们是否同时拥有你的Fun2(拿一个string和拿一个的那个int).这是有效的C#超载,反而使得类型的扣除Func<T, string>Fun2可以转换几乎是不可能的; 两者都有效.

但是,虽然.NET确实允许在返回类型上重载,但C#没有.因此,Func<T, TResult>一旦T确定了类型,就没有有效的C#程序对从方法(或lambda)创建的返回类型不明确.相对容易,再加上非常实用,使得编译器可以为我们推断.