2017-03-16 89 views
16

考虑这个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版本中指定我希望返回值是具体类型?或者是调用静态方法来做到这一点?

+0

__static__方法不是唯一的方法。你也可以使用非静态的。例如'Func f =()=> new Foo();测试(f.Invoke);'但真正的问题是,为什么你甚至检查(可能是多播)委托实例上的'.Method.ReturnType'?这有什么用处;你为什么在乎? –

+1

@Jeppe - 第三方库有一个公共API,其方法类似于'Test'。我没有想到通过传递一个lambda而不是一个实际函数的引用的副作用。必须挖掘才能发现他们检查了方法的返回类型。如果它是我自己的代码,我不会创建这样的事情。 –

回答

12

lambda表达式的返回类型不是从lambda acutally返回的内容推断出来的,而是从它被派生出来的类型推断出来的。也就是说,你不能将这样的拉姆达(当泛型类型参数involed除外;见埃里克利珀的评论):

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

你必须经常做这样的事情(lambda表达式总是分配给委托类型) :

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

因此在Test(() => new Foo());拉姆达的返回类型是从它被分配给(IThing,的ThingCreator返回类型)的参数的类型确定。

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

+5

撇开原始海报给出的具体问题,我注意到你声称lambda表达式的类型是从不从* lambda返回的结果推断为false。考虑例如称为M((= 1))的'静态无效的M(Func f)'。在这种情况下,键入推理的原因是形式参数类型 - 它们全部为零 - 是已知的,因此可以从lambda的返回类型(即int)中推断出T. –

+0

@EricLippert:这只有在涉及泛型类型参数时才会发生? –

+1

正确;我提到的情况是IIRC唯一一次C#从lambda的返回类型推断*。在所有其他情况下,检查返回类型是否与代理的返回类型兼容,返回类型必须已知。 –

0

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

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

9

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表达式的隐藏方法,这些隐藏的方法返回任何被要求委托,而不是无论拉姆达返回

这是为什么?

更密切的例子就是证明:

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,因为委托类型需要。

别急,你说......我不能改变测试的签名或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约束。你明白为什么约束必须在那里吗?这是作为练习给读者的。

+0

非常好的解释。谢谢! –