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

时间:2015-09-17 10:40:46

标签: 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);
    }
}

编译好。

我想知道为什么我不能删除<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似乎同样困难。

2 个答案:

答案 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);
    }
}

因为它可以按顺序推断:

  1. T1int
  2. Fun2被分配给某些Func<int, T2>的{​​{1}}。
  3. 如果T2Fun2,则可以将
  4. Func<int, T2>分配给T2。因此string是字符串。
  5. 特别是,一旦有了参数类型,就可以从函数中推断出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。这将其缩小为一种方法,因此可以推断出使用哪种方法。