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

时间:2012-02-10 15:13:06

标签: c# interface implicit-conversion

好。我看过this帖子,我对它如何适用于我的例子感到困惑(下图)。

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:

我理解 这是不允许的,因为有针对它的规则。我很困惑为什么是不允许的。

4 个答案:

答案 0 :(得分:41)

  

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

一般规则是:用户定义的转化不得以任何方式替换内置转化。有一些微妙的方法可以违反此规则涉及泛型类型,但您具体说明你对泛型类型的场景不感兴趣。

例如,您无法进行从MyClassObject的用户定义转换,因为已经 MyClass隐式转换为{ {1}}。 “内置”转换将始终获胜,因此允许您声明用户定义的转换将毫无意义。

此外,您甚至无法进行用户定义的隐式转换,以取代内置的显式转换。例如,您不能进行从ObjectObject的用户定义隐式转换,因为已经存在从MyClass到{Object的内置显式转换{1}}。对于代码的读者而言,让您任意将现有的显式转换重新分类为隐式转换,这简直太令人困惑了。

这是特别涉及身份的情况。如果我说:

MyClass

然后我希望这意味着“object someObject = new MyClass(); MyClass myclass = (MyClass) someObject; 实际上属于someObject类型,这是一个明确的引用转换,现在MyClassmyclass引用相等”。如果你被允许说

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是引用等于因为将Foo2强制转换为其基类型Foo不会更改引用。现在假设这是合法的:

foo

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

class Foo 
{
    public static implicit operator Foo(IBlah blah) { return new Foo(); }
}

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

仅此一项,您不得通过任何用户定义的转换替换任何内置转换的规则是足够,以拒绝您创建带有接口的转换的能力。

但是等等!假设IBlah blah = new Foo2(); Foo foo = blah; 密封。然后 FooIBlah之间没有显式或隐式转换,因为实现Foo的派生Foo2不可能。在这种情况下,我们是否应该允许IBlahFoo之间的用户定义转换?这种用户定义的转换不可能替换显式或隐式的任何内置转换。

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

为什么呢?因为有一个合理的期望,当一个人将一个值转换为一个接口时,那个你正在测试所讨论的对象是否实现了接口,而不是要求一个接口实现接口的完全不同的对象。在COM术语中,转换为接口是IBlah - “你实现了这个接口吗?” - 而不是{{1 - “你能找到我实现这个界面的人吗?

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

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

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

答案 1 :(得分: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();

您已经暴露了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标记可转换项目。该接口 使不同的实现类的转换成为合法的。


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

Why can't I use interface with explicit operator?

  

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

答案 2 :(得分: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;

编译器应该做什么,以及执行时应该怎么做? foo 已经指的是IFooCompatible的实现,所以从这个角度来看它应该只是使它成为引用转换 - 但编译器不会知道< / em>就是这样,所以它实际上只是调用隐式转换吗?

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

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

答案 3 :(得分:1)

这里可能有用的是.net提供一种“干净”的方式来将接口与静态类型相关联,并且在接口类型上有各种类型的操作映射到静态类型上的相应操作。在某些情况下,这可以通过扩展方法来完成,但这既丑陋又有限。将接口与静态类相关联可以提供一些显着的优势:

  1. 目前,如果接口希望为消费者提供功能的多次重载,则每个实现必须实现每次重载。将静态类与接口配对,并允许该类以扩展方法的方式声明方法将允许类的使用者使用静态类提供的重载,就像它们是接口的一部分一样,而不需要实现者提供它们。这可以通过扩展方法完成,但需要在使用者端手动导入静态方法。
  2. 在很多情况下,接口会有一些静态方法或属性与它紧密相关(例如`Enumerable.Empty`)。能够为接口使用相同的名称和相关属性的“类”似乎比为两个目的使用单独的名称更清晰。
  3. 它将提供支持可选接口成员的路径;如果成员存在于接口但不存在实现,则vtable槽可以绑定到静态方法。这将是一个非常有用的功能,因为它可以在不破坏现有实现的情况下扩展接口。

鉴于遗憾的是,这样的特性仅存在于使用COM对象所必需的范围内,我可以想到的最佳替代方法是定义一个包含接口类型的单个成员的结构类型,并通过代理实现接口作为代理人。从接口到结构的转换不需要在堆上创建额外的对象,并且如果函数提供接受该结构的重载,则它们可以以其净结果将是保值的方式转换回接口类型。而不是需要拳击。不幸的是,将这样的结构传递给使用接口类型的方法将需要装箱。可以通过让struct的构造函数检查传递给它的接口类型对象是否是该结构的嵌套实例来限制装箱的深度,如果是这样,则打开一层装箱。这可能有点icky,但在某些情况下可能会有用。