【问题标题】:Delegate return type different with lambda function委托返回类型与 lambda 函数不同
【发布时间】:2017-03-16 22:33:32
【问题描述】:

考虑一下这个 MCVE:

using System;

public interface IThing { }

public class Foo : IThing
{
    public static Foo Create() => new Foo();
}

public class Bar : IThing
{
    public static Bar Create() => new Bar();
}

public delegate IThing ThingCreator();

class Program
{
    static void Test(ThingCreator creator)
    {
        Console.WriteLine(creator.Method.ReturnType);
    }

    static void Main()
    {
        Test(Foo.Create);      // Prints: Foo
        Test(Bar.Create);      // Prints: Bar

        Test(() => new Foo()); // Prints: IThing
        Test(() => new Bar()); // Prints: IThing
    }
}

为什么静态工厂方法反射返回类型给出具体类型,而内联调用构造函数给出接口?我希望它们都是一样的。

另外,有没有办法在 lambda 版本中指定我希望返回值是具体类型?还是调用静态方法是唯一的方法?

【问题讨论】:

  • 静态方法并不是唯一的方法。您也可以使用非静态的。例如Func<Foo> f = () => new Foo(); Test(f.Invoke); 但真正的问题是,您为什么还要检查(可能是多播)委托实例上的.Method.ReturnType?那有什么用;你为什么在乎?
  • @Jeppe - 第三方库有一个公共 API,其方法类似于 Test。我没想到通过传递 lambda 而不是对实际函数的引用会产生副作用。不得不挖掘发现他们检查了方法的返回类型。如果是我自己的代码,我不会创建这样的东西。

标签: c# .net reflection lambda delegates


【解决方案1】:

lambda 表达式的返回类型不是从 lambda 实际返回的内容中推断出来的,而是从分配给它的类型推断出来的。即,您不能像这样分配 lambda(除非涉及泛型类型参数;请参阅 Eric Lippert 的 cmets):

// This generates the compiler error:
// "Cannot assign lambda expression to an implicitly-typed variable".
var lambda = () => new Foo();

你必须总是做这样的事情(lambdas总是分配给一个委托类型):

Func<MyType> lambda = () => new Foo();

因此,在Test(() =&gt; new Foo()); 中,lambda 的返回类型由分配给它的参数的类型决定(IThingThingCreator 的返回类型)。

Test(Foo.Create); 中根本没有 lambda,而是声明为public static Foo Create() ... 的方法。这里类型是明确指定的,是Foo(不管是静态方法还是实例方法都没有区别)。

【讨论】:

  • 撇开原始海报给出的具体问题不谈,我注意到您声称从 lambda 返回的内容推断出 lambda 表达式的类型是 never 的说法是错误的。例如,考虑将static void M&lt;T&gt;(Func&lt;T&gt; f) 称为M( () =&gt; 1 )。在这种情况下,类型推断的原因是形式参数类型(它们都为零)是已知的,因此可以从 lambda 的返回类型(即 int)推断 T。
  • @EricLippert:这是否仅在涉及泛型类型参数时才会发生?
  • 正确;我提到的情况是 IIRC 唯一一次 C# 推断 from lambda 的返回类型。在所有其他情况下,会检查返回类型是否与委托的返回类型兼容,这必须是已知的。
【解决方案2】:

Olivier 的回答基本上是正确的,但它可以使用一些额外的解释。

为什么静态工厂方法反射返回类型给出具体类型,而内联调用构造函数给出接口?我希望它们都是一样的

您的 Program 类等价于以下类:

class Program
{
  static void Test(ThingCreator creator)
  {
    Console.WriteLine(creator.Method.ReturnType);
  }
  static IThing Anon1() 
  {
    return new Foo();
  }
  static IThing Anon2()
  {
    return new Bar();
  }
  static void Main()
  {
    Test(new ThingCreator(Foo.Create));
    Test(new ThingCreator(Bar.Create));
    Test(new ThingCreator(Program.Anon1));
    Test(new ThingCreator(Program.Anon2));
  }
}

现在应该清楚为什么程序会打印它所做的事情了。

这里的故事的寓意是,当我们为 lambda 生成隐藏方法时,这些隐藏方法返回委托所需的任何内容,而不是 lambda 返回的任何内容 em>。

这是为什么呢?

一个更贴切的例子可以证明:

static void Blah(Func<object> f) 
{ 
  Console.WriteLine(f().ToString());
}
static void Main()
{
  Blah( () => 123 );
}

我希望你同意这必须生成为

static object Anon() { return (object)123; }

而不是

static int Anon() { return 123; }

因为后者无法转换为Func&lt;object&gt;!装箱指令无处可去,但对ToString 的调用需要f() 返回一个引用类型。

因此,一般规则是,当 lambda 被具体化为隐藏方法时,必须具有通过转换为委托类型而授予它的返回类型。

另外,有没有办法在 lambda 版本中指定我希望返回值是具体类型?还是调用静态方法是唯一的方法?

当然。

interface IThing {}
class Foo : IThing {}
delegate T ThingCreator<T>() where T : IThing;
public class Program
{
  static  void Test<T>(ThingCreator<T> tc) where T : IThing
  {
    Console.WriteLine(tc.Method.ReturnType);
  }
  public static void Main()
  {
    Test(() => new Foo());      
  }
}

为什么不一样?

因为类型推断推断 TFoo,因此我们将 lambda 转换为 ThingCreator&lt;Foo&gt;,其返回类型为 Foo。因此,生成的方法返回 Foo,正如委托类型所期望的那样。

但是等等,你说...我无法更改 Test 或 ThingCreator 的签名

不用担心!您仍然可以完成这项工作:

delegate T ThingCreator<T>() where T : IThing;
delegate IThing ThingCreator();    
public class Program
{
    static void Test(ThingCreator tc)
    {
      Console.WriteLine(tc.Method.ReturnType);
    }
    static ThingCreator DoIt<T>(ThingCreator<T> tc) where T : class, IThing
    { 
      return tc.Invoke; 
    }
    public static void Main()
    {
      Test(DoIt(() => new Foo()));
    }
}

不利的一面是,现在您的每个非泛型 ThingCreator 委托都是调用 ThingCreator&lt;T&gt; 委托的委托,这有点浪费时间和内存。但是您可以通过类型推断、方法组转换和 lambda 转换来使您成为所需返回类型的方法以及该方法的委托。

注意class 约束。你明白为什么必须有那个约束吗?这留给读者作为练习。

【讨论】:

  • 很好的解释。谢谢!
【解决方案3】:

我个人的猜测是调用位置。当您将() =&gt; new Foo() 传递给函数时,它会将其作为ThingCreator 抓取并调用它以获取IThing。但是,当您将具体类型的工厂方法发送到 Test 方法时,当测试方法调用它时,它会转到具体类型的 Create(),而后者又会返回完全可以接受的具体对象,因为它也是 @987654326 @

我猜你需要的比我猜的要多。对不起,如果我错了!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-10-29
    • 2013-08-19
    • 2015-05-28
    • 1970-01-01
    • 2022-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多