考虑一下这个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版本中指定我希望返回值是具体类型?或者调用静态方法是唯一的方法吗?
答案 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的返回类型是根据它所分配的参数类型(IThing
,ThingCreator
的返回类型)确定的。
在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());
}
}
为什么会有所不同?
因为类型推断推断T
是Foo
,因此我们将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
我猜你需要的不仅仅是我的猜测。对不起,如果我错了!