关于C#中的隐式转换运算符和接口的更多信息(再次)
本文关键字:信息 再次 接口 运算符 关于 转换 | 更新日期: 2023-09-27 18:24:06
好的。我读过这篇文章,我对它如何应用于我的例子(如下)感到困惑。
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();
}
}
我已经彻底阅读了我链接到的帖子。我已经阅读了C#4规范的10.10.3节。所有给出的例子都与泛型和继承有关,而上面没有。
有人能解释为什么在这个例子的上下文中这是不允许的吗?
请不要以"因为规范这么说"或简单引用规范的形式发布帖子。显然,规范不足以让我理解,否则我就不会发布这个问题。
编辑1:
我理解这是不允许的,因为有反对它的规则。我很困惑为什么不允许。
我知道这是不允许的,因为有规则反对它。我很困惑为什么不允许。
一般规则是:用户定义的转换不得以任何方式取代内置转换在涉及泛型类型的情况下,有一些微妙的方式可能会违反此规则,但您明确表示对泛型类型场景不感兴趣。
例如,您不能进行从MyClass
到Object
的用户定义转换,因为已经存在从MyClass
到Object
的隐式转换。"内置"转换将始终获胜,因此允许您声明用户定义的转换将毫无意义。
此外,您甚至不能进行用户定义的隐式转换,以取代内置的explicit转换器。例如,您不能进行从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
。在这种情况下,我们是否应该允许在Foo
和IBlah
之间进行用户定义的转换?这种用户定义的转换不可能取代任何内置的显式或隐式转换。
没有。我们在规范的10.10.3节中添加了一条额外的规则,明确禁止任何用户定义的接口转换,无论这是否取代了内置转换。
为什么?因为人们有一个合理的期望,即当将值转换为接口时,您测试的是有问题的对象是否实现了接口求实现该接口的完全不同的对象在COM术语中,转换为接口是QueryInterface
-"你实现这个接口吗?"--而不是QueryService
-"你能给我找一个实现这个接口的人吗?"
类似地,人们有一个合理的期望,即当从接口转换时,会询问该接口是否实际上是由给定目标类型的对象实现的,而不是询问与实现该接口的对象完全不同的目标类型对象
因此,进行转换到接口或从接口转换的用户定义转换总是非法的。
然而,泛型在很大程度上搅乱了局面,规范的措辞不是很清楚,并且C#编译器在其实现中包含了许多错误。考虑到涉及泛型的某些边缘情况,规范和实现都不正确,这对我这个实现者来说是一个难题。事实上,我今天正在与Mads合作,澄清规范的这一部分,因为我下周将在Roslyn实施它。我将尝试尽可能少地进行突破性的更改,但为了使编译器行为和规范语言相互一致,可能需要少量更改。
在您的示例的上下文中,它将无法再次工作,因为隐式运算符已放置在接口上。。。我不确定你认为你的样本与你链接的样本有何不同,除非你试图通过接口将一个具体类型传递给另一个。
在connect:上有一个关于这个主题的讨论
http://connect.microsoft.com/VisualStudio/feedback/details/318122/allow-user-defined-implicit-type-conversion-to-interface-in-c
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.
强制转换理解类型层次结构,fInt
的引用不能强制转换为Bar
,因为它实际上是Foo
。你可以提供一个用户定义的操作员来可能提供这个:
public static implicit operator Foo(Bar b) { };
在示例代码中这样做是可行的,但这开始变得很愚蠢。
另一方面,转换完全独立于类型层次结构。它的行为完全是任意的——你想编码什么就编码什么。这就是您实际所处的情况,将Bar
转换为Foo
,您刚好用IFooCompatible
标记可转换项。该接口并不能使跨不同实现类的强制转换合法。
关于用户定义的转换运算符中不允许使用接口的原因:
为什么可以';我是否使用带有显式运算符的接口?
简短的版本是不允许这样做,这样用户就可以确定引用类型和接口之间的转换如果且仅当引用类型实际实现了接口,并且当转换发生时对象实际上正在被引用。
好吧,这里有一个我认为限制在这里的例子:
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提供一种"干净"的方式将接口与静态类型相关联,并将接口类型上的各种类型的操作映射到静态类型上的相应操作,那么这里可能会有所帮助。在某些场景中,这可以通过扩展方法来实现,但这既丑陋又有限。将接口与静态类相关联可以提供一些显著的优势:
- 目前,如果一个接口希望为消费者提供一个函数的多个重载,那么每个实现都必须实现每个重载。将静态类与接口配对,并允许该类以扩展方法的样式声明方法,将允许该类的使用者使用静态类提供的重载,就好像它们是接口的一部分一样,而不需要实现者提供它们。这可以通过扩展方法来完成,但需要在使用者端手动导入静态方法。
- 在许多情况下,接口会有一些静态方法或属性,这些方法或属性与它有很强的关联(例如"Enumerable.Empty")。能够为接口使用相同的名称和关联属性的"class"似乎比必须为这两个目的使用单独的名称更干净。
- 它将提供一条支持可选接口成员的途径;如果接口中存在成员,但实现中不存在成员,则vtable槽可以绑定到静态方法。这将是一个非常有用的特性,因为它允许在不破坏现有实现的情况下扩展接口。
不幸的是,这样的功能只存在于处理COM对象所需的范围内,我能想到的最好的替代方法是定义一个结构类型,该结构类型包含接口类型的单个成员,并通过充当代理来实现接口。从接口到结构的转换不需要在堆上创建额外的对象,如果函数要提供接受该结构的重载,它们可以转换回接口类型,其净结果将是保留值的,而不需要装箱。不幸的是,将这样的结构传递给使用接口类型的方法将需要装箱。可以通过让结构的构造函数检查传递给它的接口类型对象是否是该结构的嵌套实例来限制装箱的深度,如果是,则打开一层装箱。这可能有点恶心,但在某些情况下可能有用。