为什么用Invoke方法编译可以,直接return Func<int,int>编译不行?

Why is compilation OK, when I use Invoke method, and not OK when I return Func<int,int> directly?

我不明白这个案例:

public delegate int test(int i);

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // <- code doesn't compile
}

为什么我用Invoke的方法编译可以,直接return csharp Func<int,int>的编译就不行?

在第二种情况下,fFunc<int, int>类型的,但是方法据说是return一个test。这些是不相关的(委托)类型,它们不能相互转换,因此会发生编译器错误。您可以转到语言规范的 section,然后搜索“delegate”。您会发现没有提及具有相同签名的委托之间的转换。

然而,在第一种情况下,f.Invoke 是一个 方法组表达式 ,它实际上没有类型。 C# 编译器会根据上下文将方法组表达式转换为特定的委托类型,通过 method group conversion.

(引用第 5 个项目符号 here,强调我的)

An expression is classified as one of the following:

  • ...

  • A method group, which is a set of overloaded methods resulting from a member lookup. [...] A method group is permitted in an invocation_expression, a delegate_creation_expression and as the left hand side of an is operator, and can be implicitly converted to a compatible delegate type.

在这种情况下,它被转换为test委托类型。

换句话说,return f 不起作用,因为 f 已经有类型,但 f.Invoke 还没有类型。

这里的问题是类型兼容性:

以下是来自 MSDN 资源的 Func 委托的定义:

public delegate TResult Func<in T, out TResult>(T arg);

如果你发现上面提到的 Func 和你定义的 Delegate 之间没有直接关系:

public delegate int test(int i);

Why 1st snippet compiles:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
 }

Delegates通过signature进行比较,signature是输入参数和Output结果,最终一个Delegate是一个Function指针,两个函数只能通过签名进行比较。在运行时,通过 Func 调用的方法被分配给 Test 委托,因为 Signature 是相同的,所以它可以无缝地工作。这是一个函数指针赋值,其中 Test 委托现在将调用 Func 委托指向的方法

Why 2nd Snippet fails to compile

Func 和测试委托之间没有类型/赋值兼容性,Func 不能作为类型系统规则的一部分填写。即使它的结果可以像第一种情况那样分配并填写 test delegate

要了解此行为,您需要了解两件事。

  1. 所有委托都派生自System.Delegate,但不同的委托具有不同的类型,因此不能相互赋值。
  2. C# 语言provides special handling for assigning a method or lambda to a delegate

因为不同的委托有不同的类型,这意味着您不能将一种类型的委托分配给另一种类型的委托。

例如,给定:

delegate void test1(int i);
delegate void test2(int i);

然后:

test1 a = Console.WriteLine; // Using special delegate initialisation handling.
test2 b = a;                 // Using normal assignment, therefore does not compile.

上面的第一行编译成功,因为它使用特殊处理将 lambda 或方法分配给委托。

事实上,这一行被编译器有效地重写为:

test1 a = new test1(Console.WriteLine);

上面的第二行没有编译,因为它试图将一种类型的实例分配给另一种不兼容的类型。

就类型而言,test1test2 之间没有兼容的分配,因为它们是不同的类型。

如果有助于思考,请考虑这个 class 层次结构:

class Base
{
}

class Test1 : Base
{
}

class Test2 : Base
{
}

以下代码将无法编译,即使 Test1Test2 派生自相同的基 class:

Test1 test1 = new Test1();
Test2 test2 = test1; // Compile error.

这解释了为什么不能将一种委托类型分配给另一种委托类型。那只是普通的 C# 语言。

但是,关键是要了解为什么允许将方法或 lambda 分配给兼容的委托。如上所述,这是 C# 语言对委托的支持的一部分。

终于可以回答你的问题了:

当您使用 Invoke() 时,您正在使用特殊的 C# 语言处理将方法调用分配给委托,以便将方法或 lambda 表达式分配给委托,而不是尝试分配不兼容的类型 - 因此它可以编译。

为了完全清楚,在您的 OP 中编译的代码:

public test Success()
{
    Func<int, int> f = x => x;
    return f.Invoke; // <- code successfully compiled 
}

实际上在概念上转换为:

public test Success()
{
    Func<int, int> f = x => x;
    return new test(f.Invoke);
}

而失败的代码试图在两个不兼容的类型之间分配:

public test Fail()
{
    Func<int, int> f = x => x;
    return f; // Attempting to assign one delegate type to another: Fails
}