更多关于C#中的隐式转换运算符和接口(再次)

gre*_*nis 23 c# interface implicit-conversion

好的.我已经读过这篇文章了,我对它如何适用于我的例子感到困惑(下图).

class Foo
{
    public static implicit operator Foo(IFooCompatible fooLike)
    {
        return fooLike.ToFoo();
    }
}

interface IFooCompatible
{
    Foo ToFoo();
    void FromFoo(Foo foo);
}

class Bar : IFooCompatible
{
    public Foo ToFoo()
    {
        return new Foo();   
    }

    public void FromFoo(Foo foo)
    {
    }
}

class Program
{
    static void Main(string[] args)
    {
        Foo foo = new Bar();
        // should be the same as:
        // var foo = (new Bar()).ToFoo();
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经彻底阅读了我链接的帖子.我已阅读C#4规范的第10.10.3节.给出的所有示例都涉及泛型和继承,而上述情况则不然.

任何人都可以解释为什么在这个例子的上下文中不允许这样做

请不要以"因为规范说明"或仅引用规范的形式发布帖子.显然,规范不足以让我理解,否则我不会发布这个问题.

编辑1:

我的理解这是不允许的,因为有反对它的规则.我很困惑为什么不允许这样做.

Eri*_*ert 41

我理解这是不允许的,因为有规则反对它.我很困惑为什么不允许这样做.

一般规则是:用户定义的转换不得以任何方式替换内置转换.有一些微妙的方法可以违反此规则涉及泛型类型,但您明确表示您对泛型类型方案不感兴趣.

你不能,例如,从使用户定义的转换MyClassObject,因为已经有从隐式转换MyClassObject."内置"转换将始终获胜,因此允许您声明用户定义的转换将毫无意义.

此外,您甚至无法进行用户定义的隐式转换来替换内置的显式转换.你不能,例如,从让用户定义的隐式转换ObjectMyClass,因为已经有一个内置的明确从转换ObjectMyClass.对于代码的读者而言,让您任意将现有的显式转换重新分类为隐式转换,这简直太令人困惑了.

这是特别地方的情况下身份参与.如果我说:

object someObject = new MyClass();
MyClass myclass = (MyClass) someObject;
Run Code Online (Sandbox Code Playgroud)

那么我希望这意味着" someObject实际上是类型MyClass,这是一个明确的引用转换,现在myclasssomeObject引用相等".如果你被允许说

public static implicit operator MyClass(object o) { return new MyClass(); }
Run Code Online (Sandbox Code Playgroud)

然后

object someObject = new MyClass();
MyClass myclass = someObject;
Run Code Online (Sandbox Code Playgroud)

这将是合法的,并且这两个对象不具有引用相等性,这是奇怪的.

我们已经有足够的规则来取消您的代码资格,从代码转换为未密封的类类型.考虑以下:

class Foo { }
class Foo2 : Foo, IBlah { }
...
IBlah blah = new Foo2();
Foo foo = (Foo) blah;
Run Code Online (Sandbox Code Playgroud)

这是有效的,并且有理由期望blah并且foo引用等于因为将Foo2强制转换为其基类型Foo不会更改引用.现在假设这是合法的:

class Foo 
{
    public static implicit operator Foo(IBlah blah) { return new Foo(); }
}
Run Code Online (Sandbox Code Playgroud)

如果这是合法的,那么此代码是合法的:

IBlah blah = new Foo2();
Foo foo = blah;
Run Code Online (Sandbox Code Playgroud)

我们刚刚将派生类的实例转换为它的基类,但它们不是引用相等的.这是奇怪和令人困惑的,因此我们将其视为非法.您可能不会声明这样的隐式转换,因为它取代了现有的内置显式转换.

仅此一项,您不得通过任何用户定义的转换替换任何内置转换的规则足以让您无法创建带有接口的转换.

可是等等!假设Foo密封的.再有之间没有转换IBlahFoo,明确的或隐含的,因为不可能由派生Foo2实现IBlah.在这种情况下,我们应该允许在Foo和之间进行用户定义的转换IBlah吗?这种用户定义的转换不可能替换任何显式或隐式的内置转换.

不会.我们在规范的第10.10.3节中添加了一条额外的规则,该规则明确禁止任何用户定义的转换到接口或从接口转换,无论是替换还是不替换内置转换.

为什么?因为有一个合理的期望,当一个人将一个值转换一个接口时,你正在测试所讨论的对象是否实现了接口,而不是要求一个完全不同的对象来实现接口. 在COM方面,转换为接口是QueryInterface- " 你实现这个接口吗? " - 而不是QueryService- " 你能找到我实现这个接口的人吗? "

类似地,人们有一个合理的期望,当一个人一个接口转换时,就会询问该接口是否实际上是由给定目标类型的对象实现的,而不是要求一个与该对象完全不同的目标类型的对象.实现接口.

因此,进行转换为接口或从接口转换的用户定义转换始终是非法的.

然而,泛型相当混乱,规范措辞不是很清楚,C#编译器在其实现中包含许多错误.鉴于涉及泛型的某些边缘情况,规范和实现都不正确,这对我(实施者)来说是一个难题.我实际上正在与Mads合作澄清规范的这一部分,因为我将在下周在Roslyn实施它.我将尝试尽可能少地进行更改,但为了使编译器行为和规范语言相互一致,可能需要少量数据.

  • +1是的...这种让我的答案不复存在!:-) (6认同)
  • 为什么您还禁止对结构上的接口使用隐式转换运算符?因为结构体还可以实现您为其创建运算符的接口?完全破坏了我创建一个结构体的尝试,该结构体的功能类似于 F# 中的选项类型;P (2认同)

Ada*_*rth 22

您的示例的上下文,它将无法再次工作,因为隐式运算符已放置在接口上...我不确定您认为您的示例与您链接的示例有何不同,而不是您试图获得一个具体的通过接口键入到另一个.

这里有关于connect的讨论:

http://connect.microsoft.com/VisualStudio/feedback/details/318122/allow-user-defined-implicit-type-conversion-to-interface-in-c

Eric Lippert可能已经解释了他在你的链接问题中说的原因:

接口值上的强制转换始终被视为类型测试,因为对象实际上几乎总是可能属于该类型并且确实实现了该接口.我们不想否认你做一个廉价的代表保留转换的可能性.

似乎与类型身份有关.具体类型通过其层次结构相互关联,因此可以在其中强制执行类型标识.对于接口(以及其他被阻止的东西,例如dynamicobject),类型标识变得毫无意义,因为任何人/每个人都可以被安置在这种类型下.

为什么这很重要,我不知道.

我更喜欢显式代码,它告诉我我正试图Foo从另一个中获取IFooCompatible,所以转换例程需要T where T : IFooCompatible返回Foo.

对于你的问题,我理解讨论的重点,但是我的讽刺反应是,如果我看到像Foo f = new Bar()野外的代码,我很可能会重构它.


替代解决方案:

不要把这个布丁捣碎了:

Foo f = new Bar().ToFoo();
Run Code Online (Sandbox Code Playgroud)

您已经暴露了Foo兼容类型实现接口以实现兼容性的想法,请在您的代码中使用它.


铸造与转换:

关于铸造和转换,也很容易使电线交叉.转换意味着类型信息在您所投射的类型之间是不可变的,因此在这种情况下,转换不起作用:

interface IFoo {}
class Foo : IFoo {}
class Bar : IFoo {}

Foo f = new Foo();
IFoo fInt = f;
Bar b = (Bar)fInt; // Fails.
Run Code Online (Sandbox Code Playgroud)

Casting理解类型层次结构和引用fInt不能Bar像实际那样被强制转换Foo.您可以提供一个用户定义的运算符来提供:

public static implicit operator Foo(Bar b) { };
Run Code Online (Sandbox Code Playgroud)

在您的示例代码中执行此操作,但这开始变得愚蠢.

另一方面,转换完全独立于类型层次结构.它的行为完全是任意的 - 你编写你想要的代码.这就是你实际处于的情况,将a转换Bar为a Foo,你恰好将可兑换物品标记为IFooCompatible.该接口不会使不同的实现类的转换合法化.


至于为什么在用户定义的转换运算符中不允许接口:

为什么我不能使用带显式运算符的接口?

简短版本是不允许的,以便用户可以确定引用类型和接口之间的转换是成功的,当且仅当引用类型实际实现该接口时,并且当转换发生时实际引用相同的对象时.


Jon*_*eet 9

好的,这是一个我为什么相信限制的例子:

class Foo
{
    public static implicit operator Foo(IFooCompatible fooLike)
    {
        return fooLike.ToFoo();
    }
}

class FooChild : Foo, IFooCompatible
{
}

...

Foo foo = new FooChild();
IFooCompatible ifoo = (IFooCompatible) foo;
Run Code Online (Sandbox Code Playgroud)

编译器应该做什么,以及执行时应该怎么做?foo 已经引用了一个实现IFooCompatible,所以从这个角度来看它应该只是使它成为一个引用转换 - 但编译器不知道是这种情况,所以它实际上只是调用隐式转换吗?

怀疑基本逻辑是:不允许操作员定义哪个操作符可能与基于执行时类型的已经有效的转换冲突.很高兴从表达式到目标类型完全零或一个可能的转换.

(编辑:亚当的答案听起来像是在谈论几乎相同的事情 - 随意将我的答案视为他的一个例子:)