为什么C#编译器不能从函数签名推断泛型类委托?

时间:2013-09-25 21:11:35

标签: c# generics functional-programming type-inference currying

我正在使用一个函数,该函数将两个函数作为参数,并返回一个新的组合函数:

public static Action<T> Compose<T>(Action<T> first, Action second)
{
    return new Action<T>(arg =>
    {
        first(arg);
        second();
    });
}

我注意到编译器在发送静态或成员函数(而不是实际的T对象)时,如果我没有指定Action<T>则会抱怨:

static void Main(string[] args)
{
    // compiler error here
    var composed = Compose(Test, () => Console.WriteLine(" world"));
    composed("hello");

    Console.ReadLine();
}

public static void Test(string arg)
{
    Console.Write(arg);
}

错误消息:

  

无法从用法推断出方法'ConsoleTest.Program.Compose(System.Action,System.Action)'的参数。尝试显式指定类型参数。

我的问题:为什么不能在这里推断出类型参数? Test的签名在编译时是已知的,不是吗?是否真的有一些功能可以替代Test,这会导致其签名不明确?

脚注:我知道我只需将new Action<string>(Test)代替Test发送给Compose(如this question所述) - 我的问题是“为什么” ,而不是“我怎么能这样做”。

2 个答案:

答案 0 :(得分:6)

我认为它可能与以下事实有关:至少从编译器的角度来看,Test实际上是一个'方法组',直到编译器确定它将具有哪些类型的参数。即使组中只有一个方法(当前范围中只有一个Test方法),也是如此。

观察:

var composed = Compose<object>(Test, () => Console.WriteLine(" world"));

产生错误:

  

Compose<object>(System.Action<object>, System.Action)”的最佳重载方法匹配包含一些无效参数

     

参数1:无法从“方法组”转换为“System.Action<object>

但这很好:

var composed = Compose<string>(Test, () => Console.WriteLine(" world"));

我的猜测是编译器在某种意义上将两个方法组表达式(Test)和隐式类型的泛型方法调用(Compose)视为“未绑定”。它无法完全确定从方法组中选择哪种方法,从参数的类型“未绑定”签名到Compose,并且它无法确定来自Compose的{​​{1}}的类型类型参数签名。它需要一个或另一个'绑定'才能编译整个语句。

答案 1 :(得分:0)

这可能与协方差有关。虽然已知Test参数的类型,但您可能希望创建更具体类型的委托。

public class BaseClass { }
public class DerivedClass : BaseClass { }

static class Program
{
    static void Main(string[] args)
    {
        var composed = Compose<DerivedClass>(Test, () => Console.WriteLine(" world"));
        composed(new DerivedClass());

        Console.ReadLine();
    }

    public static void Test(BaseClass arg)
    {
        Console.Write(arg);
    }
}