将 Func<MyType> 隐式转换为 MyType

9

给定以下类:

public class MyType
{
    public static implicit operator MyType(Func<MyType> wrapper) {
        return wrapper();
    }
}

由于Func<MyType>MyType的隐式转换,我认为以下操作是可能的:

public MyType MyTypeWrapper() {
    return new MyType();
}

public void MyTestMethod() {
    MyType m = MyTypeWrapper; // not a call!
}

然而,我得到了以下错误信息:

无法将方法组'MyTypeWrapper'转换为非委托类型'Test.MyType'。您是否打算调用该方法?

很不幸的是,当我搜索这个问题时(正如我所料的那样),结果出现了大量问题答案是:

嘿,你犯了一个错误;在WhateverMethod后面加上()

现在,当我输入这个时,我注意到一个显式转换确实可以编译:
MyType m = (MyType) MyTypeWrapper;

为什么我不能像我描述的那样将 Func<MyType> 隐式转换为 MyType
4个回答

12

很不幸,我相当确定你已经发现了编译器的一个错误,并且该规范的这一部分非常难以理解。

C#4 规范的6.4.4节解释了为什么你的隐式转换是不合法的。

算法如下。首先查看源类型和目标类型。没有源类型,因为方法组没有类型。目标类型是MyType。因此,在MyType中搜索用户定义的隐式转换。现在的问题是:有哪些适用于从包含 S 的类型转换的用户定义运算符集合?其中S是源类型,我们已经确定没有源类型。所以这已经证明了转换应该失败。但即使编译器出于某种原因认为您的Func<MyType>转换是可行的,规则也是执行标准隐式转换方法组转换故意不被归类为标准转换

所以这就是为什么它应该是不合法的。

那么为什么显式转换是合法的呢?

没有理由。这似乎是一个错误。

这很不幸,对于这个错误我非常抱歉。我将向我的前同事报告这个问题;如果他们有与我的分析冲突的分析,我将更新答案。

更新: 我的前同事告诉我,在下一个版本的规范中,源表达式被假定具有类型的问题将通过重新措辞得到解决。目前还没有关于显式转换行为是否是错误的消息。


我有点惊讶显式转换起作用了。你不同意我的回答的最后一行吗?(我没有在规范中查找它) - SLaks
2
@SLaks:编译器知道在隐式情况下查看MyType,因为它知道这是隐式转换的唯一可能目标类型。与此情况相对比:如果您有一个从Giraffe到Fruit的显式用户定义转换,并且您将对Animal的引用显式转换为Banana类型,则编译器将搜索Animal、Banana和Fruit以查找转换运算符,但不会搜索Giraffe,因此如果用户定义的转换在Giraffe上声明,那么就没戏了。 - Eric Lippert
我很高兴尝试了显式转换!感谢更新@EricLippert,期待更多信息 :) - Dan Lugg
4
如果编译器设计专家阅读规范都有困难,那我们这些凡人就没什么希望了。 - Robert Harvey

11

您已经在使用内置的从方法组Func<MyType> 的隐式转换。

编译器不会同时进行两个隐式转换。

一旦您对您的类进行了显式转换,编译器就知道要寻找一个可以显式转换为您的类的任何类型的隐式转换。


啊,我明白了。有什么方法可以缓解这个编译器的“特性”吗?(我猜想是“没有” - Dan Lugg
1
@Bracketworks:不。方法组是一种非常不寻常的表达式类型,因为它们本身没有类型(就像“null”和lambda一样)。 - SLaks
1
@Bracketworks 即使他们想这样做,这并不是真正可行的。因为编译器正在寻找从一种类型到另一种类型的转换,当它这样做时,它会寻找恰好能实现这一点的转换,而不是任何转换到任何你想要的类型的类型。这对于编译器和程序员来说都非常困难。想象一下我有一个坏意的类,其中包含从对象到我和从我到对象的转换。现在任何赋值都可能涉及两个操作同时运行,让我能够做出各种卑鄙的事情。 - Servy
我想只能在原地调用它了,这样也能理解。 - Dan Lugg
2
仅澄清一下:编译器链接一些隐式转换。例如,如果您有一个从int到Frob的用户定义的隐式转换,则可以免费获得从short的转换。 short --> int --> Frob。允许的转换在规范的第6.3节中有记录,并且方法组转换故意从该列表中省略。 - Eric Lippert
@SLaks:不用谢。请参阅http://blogs.msdn.com/b/ericlippert/archive/2007/04/16/chained-user-defined-explicit-conversions-in-c.aspx,了解此规则的一些分析。 - Eric Lippert

1
由于C#编译器无法将MyTypeWrapper转换为Func<MyType>(MyTypeWrapper),因此需要注意方法组和实际委托之间的区别。
以下代码可以正常编译和运行:
MyType m = new Func<MyType>(MyTypeWrapper);

从方法组到与该组匹配的委托类型有隐式转换,还有您定义的从该委托到类型的隐式转换。这里的一般想法是编译器一次只会使用一个隐式转换。当它有A并需要C时,它会查找从A到C的转换,而不是从A到任何类型B,再从该类型到C的转换。该算法从O(n)变为O(n^2)(更不用说可能对程序员来说相当令人困惑了)。使用显式转换为MyType时,您的代码之所以有效,是因为您不再链接隐式转换。

1

MyTestMethod的签名与Func<MyType>的签名匹配,但不是Func<MyType>。 Func自己定义了一些隐式转换,使您可以将这些方法分配为Funcs,但必须显式转换以应用签名,因为编译器不会为您链接隐式转换:

MyType m = (Func<MyType>)MyTypeWrapper; // not a call!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接