委托返回类型与lambda函数不同

时间:2017-03-16 22:33:32

标签: c# .net reflection lambda delegates

考虑一下这个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
    }
}

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

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

3 个答案:

答案 0 :(得分:13)

lambda表达式的返回类型不是从lambda acutally返回的内容推断出来的,而是从它所指定的类型推断出来的。即,你不能像这样分配一个lambda(除非调用泛型类型参数;参见Eric Lippert的评论):

// 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(() => new Foo());中,lambda的返回类型是根据它所分配的参数类型(IThingThingCreator的返回类型)确定的。

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

答案 1 :(得分:10)

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

  

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

您的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返回的任何内容

为什么?

一个更密切关注的例子将证明:

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<object>!拳击指令无处可去,但对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<Foo>,其返回类型为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<T>委托的委托,这有点浪费时间和内存。但是,您可以获得类型推断,方法组转换和lambda转换,以使您获得所需返回类型的方法,以及该方法的委托。

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

答案 2 :(得分:0)

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

我猜你需要的不仅仅是我的猜测。对不起,如果我错了!