更多关于C#中的隐式转换运算符和接口(再次)
好的。 我已经读过这篇文章了,我对它如何适用于我的例子感到困惑(下图)。
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(); } }
我已经彻底阅读了我链接的post。 我已阅读C#4规范的第10.10.3节。 给出的所有示例都涉及generics和inheritance,而上述情况则不然。
任何人都可以解释为什么在这个例子的上下文中不允许这样做 ?
请不要以“因为规范说明”或仅引用规范的forms发布post。 显然,规范不足以让我理解,否则我不会发布这个问题。
编辑1:
我知道这是不允许的,因为有规则反对它。 我很困惑为什么不允许这样做。
您的示例的上下文,它将无法再次工作,因为隐式运算符已放置在接口上…我不确定您认为您的示例与您链接的示例有何不同,而不是您尝试获取一个具体的通过接口键入到另一个。
这里有关于connect的讨论:
Eric Lippert可能已经解释了他在你的链接问题中说的原因:
接口值的强制转换始终被视为类型测试,因为对象实际上几乎总是可能属于该类型并且确实实现了该接口。 我们不想否认你做一个廉价的代表保留转换的可能性。
这似乎与类型身份有关。 具体类型通过其层次结构相互关联,因此可以在其中强制执行类型标识。 对于接口(以及其他被阻止的东西,例如dynamic
和object
),类型标识变得毫无意义,因为任何人/每个人都可以被安置在这种类型下。
为什么这很重要,我不知道。
我更喜欢显式代码,它告诉我我正试图从另一个IFooCompatible
获取Foo
,所以转换例程需要T where T : IFooCompatible
返回Foo
。
对于你的问题,我理解讨论的重点,但是我的滑稽反应是,如果我在野外看到像Foo f = new Bar()
这样的代码,我很可能会重构它。
另一种解决方案:
不要把这个布丁捣碎了:
Foo f = new Bar().ToFoo();
您已经暴露了Foo
兼容类型实现接口以实现兼容性的想法,请在您的代码中使用它。
铸造与转换:
关于铸造和转换,也很容易划线。 转换意味着类型信息在您所投射的类型之间是不可或缺的,因此在这种情况下,转换不起作用:
interface IFoo {} class Foo : IFoo {} class Bar : IFoo {} Foo f = new Foo(); IFoo fInt = f; Bar b = (Bar)fInt; // Fails.
Casting理解类型层次结构,并且fInt
的引用不能转换为Bar
因为它实际上是Foo
。 您可以提供一个用户定义的运算符来提供:
public static implicit operator Foo(Bar b) { };
在您的示例代码中执行此操作,但这开始变得愚蠢。
另一方面,转换完全独立于类型层次结构。 它的行为完全是任意的 – 你编写你想要的代码。 实际上就是这种情况,将Bar
转换为Foo
,您恰好用IFooCompatible
标记可转换项。 该接口不会使不同的实现类的转换合法化。
至于为什么在用户定义的转换运算符中不允许接口:
为什么我不能使用带显式运算符的接口?
简短版本是不允许的,以便用户可以确定引用类型和接口之间的转换是成功的,当且仅当引用类型实际实现该接口时,并且当转换发生时实际引用相同的对象时。
我知道这是不允许的,因为有规则反对它。 我很困惑为什么不允许这样做。
一般规则是: 用户定义的转换不得以任何方式替换内置转换。 有一些微妙的方法可以违反此规则涉及generics类型,但您明确表示您对generics类型方案不感兴趣。
例如,您不能进行从MyClass
到Object
的用户定义转换,因为已经存在从MyClass
到Object
的隐式转换。 “内置”转换将始终获胜 ,因此允许您声明用户定义的转换将毫无意义。
而且,您甚至无法进行用户定义的隐式转换来替换内置的显式转换。 例如,您不能进行从Object
到MyClass
的用户定义的隐式转换,因为已经存在从Object
到MyClass
的内置显式转换。 对于代码的读者而言,让您任意将现有的显式转换重新分类为隐式转换,这简直太令人困惑了。
特别是在涉及身份的情况下。 如果我说:
object someObject = new MyClass(); MyClass myclass = (MyClass) someObject;
那么我希望这意味着“ someObject
实际上是MyClass
类型,这是一个显式的引用转换,现在myclass
和someObject
是引用相等”。 如果你被允许说
public static implicit operator MyClass(object o) { return new MyClass(); }
然后
object someObject = new MyClass(); MyClass myclass = someObject;
这将是合法的 ,并且这两个对象不具有引用相等性 ,这是奇怪的 。
我们已经有足够的规则来取消您的代码,从代码转换为未密封的类类型。 考虑以下:
class Foo { } class Foo2 : Foo, IBlah { } ... IBlah blah = new Foo2(); Foo foo = (Foo) blah;
这是有效的,并且有理由期望blah
和foo
是引用等于因为将Foo2强制转换为其基类型Foo不会更改引用。 现在假设这是合法的:
class Foo { public static implicit operator Foo(IBlah blah) { return new Foo(); } }
如果这是合法的,那么此代码是合法的:
IBlah blah = new Foo2(); Foo foo = blah;
我们刚刚将派生类的实例转换为其基类,但它们不是引用相等的。 这是奇怪和令人困惑的,因此我们将其视为非法。 您可能不会声明这样的隐式转换, 因为它取代了现有的内置显式转换。
仅此一项,您不得通过任何用户定义的转换替换任何内置转换的规则足以让您无法创建带有接口的转换。
可是等等! 假设Foo
是密封的 。 然后, IBlah
和Foo
之间没有明确或隐含的转换,因为实现IBlah
的派生Foo2
可能IBlah
。 在这种情况下,我们是否应该允许在Foo
和IBlah
之间进行用户定义的转换? 这种用户定义的转换不可能替换任何显式或隐式的内置转换。
不会。我们在规范的第10.10.3节中添加了一条额外的规则,该规则明确禁止对接口进行任何用户定义的转换,无论是替换还是不替换内置转换。
为什么? 因为有一个合理的期望,当一个人将一个值转换为一个接口时, 你正在测试所讨论的对象是否实现了接口 ,而不是要求一个完全不同的实现接口的对象。 在COM术语中,转换为接口是QueryInterface
– “ 你实现了这个接口吗? ” – 而不是QueryService
– “ 你能找到我实现这个接口的人吗? ”
类似地,人们有一个合理的期望,当一个人从一个接口转换时, 就会询问该接口是否实际上是由给定目标类型的对象实现的 ,而不是要求一个与该对象完全不同的目标类型的对象。实现接口。
因此,进行转换为接口或从接口转换的用户定义转换始终是非法的。
然而, generics相当混乱,规范措辞不是很清楚,C#编译器在其实现中包含许多错误 。 鉴于涉及generics的某些边缘情况,规范和实现都不正确,这对我(实施者)来说是一个难题。 我实际上正在与Mads合作澄清规范的这一部分,因为我将在下周在Roslyn实施它。 我将尝试尽可能少地进行更改,但为了使编译器行为和规范语言相互一致,可能需要少量数据。
好的,这是一个我为什么认为限制在这里的例子:
class Foo { public static implicit operator Foo(IFooCompatible fooLike) { return fooLike.ToFoo(); } } class FooChild : Foo, IFooCompatible { } ... Foo foo = new FooChild(); IFooCompatible ifoo = (IFooCompatible) foo;
编译器应该做什么,以及执行时应该怎么做? foo
已经引用了IFooCompatible
的实现,所以从这个角度来看它应该只是使它成为引用转换 – 但编译器不知道是这种情况,所以它实际上只是调用隐式转换吗?
我怀疑基本逻辑是:不允许操作员定义哪个操作符可能与基于执行时类型的已经有效的转换冲突。 很高兴从表达式转换为目标类型的确切零或一个可能的转换。
(编辑:亚当的回答听起来像是在谈论几乎相同的事情 – 随意将我的回答视为他的一个例子:)
这里可能有用的是.net提供一种“干净”的方式来将接口与静态类型相关联,并且在接口类型上具有各种类型的操作映射到静态类型上的相应操作。 在某些情况下,这可以通过扩展方法来完成,但这既丑陋又有限。 将接口与静态类相关联可以提供一些显着的优势:
- 目前,如果接口希望为消费者提供多个function的过载,则每个实现必须实现每个过载。 将静态类与接口配对,并允许该类以扩展方法的方式声明方法将允许类的使用者使用静态类提供的重载,就像它们是接口的一部分一样,而不需要实现者提供它们。 这可以通过扩展方法完成,但它要求在消费者端手动导入静态方法。
- 在许多情况下,接口将具有与其非常强烈关联的一些静态方法或属性(例如,“Enumerable.Empty”)。 能够为接口使用相同的名称和相关属性的“类”似乎比为两个目的使用单独的名称更清晰。
- 它将提供支持可选接口成员的途径; 如果成员存在于接口但不存在实现,则vtable槽可以绑定到静态方法。 这将是一个非常有用的function,因为它可以在不破坏现有实现的情况下扩展接口。
鉴于遗憾的是,这样的特性仅存在于使用COM对象所必需的范围内,我可以想到的最佳替代方法是定义一个包含接口类型的单个成员的结构类型,并通过充当代理来实现接口。 。 从接口到结构的转换不需要在堆上创建额外的对象,并且如果函数提供接受该结构的重载,则它们可以以净结果将是保值的方式转换回接口类型。而不是需要拳击。 不幸的是,将这样的结构传递给使用接口类型的方法将需要装箱。 可以通过让struct的构造函数检查传递给它的接口类型对象是否是该结构的嵌套实例来限制装箱的深度,如果是这样,则打开一层装箱。 这可能有点icky,但在某些情况下可能会有用。