C#中类的显式接口实现

3

有没有类似于C#中显式接口实现的类似方法来实现类呢?

考虑以下情况:

公司X提供了一个包含如下类的库:

public class LibraryClass
{
    public virtual void A() { }

    public void DoWork()
    {
        // does something
    }
}

Y公司在其产品中使用了这个库,并继承自LibraryClass

public class UserClass : LibraryClass
{
    public virtual void B() { }
}

到目前为止,一切都运转良好。但某天X发布了一个新的库版本并向LibraryClass添加了一个虚方法B()

public class LibraryClass
{
    public virtual void A() { }
    public virtual void B() { }

    public void DoWork()
    {
        // does something
        // now uses B with certain semantic assumptions
    }
}

现在Y更新到了新的库版本。当使用对新版本的引用进行编译时,编译器会发出一个警告,指出隐藏了继承的方法,因此应该指定new关键字或覆盖该方法。由于现有的方法和新引入的方法之间存在语义差异,Y决定引入new关键字,因为任何现有的覆盖可能不会提供所期望的语义,这将导致代码出错。另一方面,Y想要使用库的一个新功能,这需要覆盖。现在这是不可能的:如果在的派生类中进行覆盖,由于new关键字,覆盖将引用;在本身中覆盖甚至是不允许的,因为它已经定义了具有该签名的公共方法。
如果在的派生类中有一种方式可以指定覆盖引用,那么这种情况就可以解决,但据我所知目前并不可行;或者如果中的可以被显式地覆盖,那么也可以解决这个问题。
public class UserClass : LibraryClass
{
    ...

    // Override this method in order to change the behavior of LibraryClass.B()
    public virtual void LibraryB() { }

    private void override LibraryClass.B()
    {
        LibraryB();
    }

    ...
}

除了重命名UserClass中的原始B()方法(如果它是由公司Z使用的库的一部分,则可能无法进行重命名),还有没有其他语言解决此问题的方法?如果没有,这是C#的限制还是CLR的限制?

对于这篇长文章表示抱歉,感谢您阅读到这里。

编辑:这不是CLR的限制。 C++ / CLI支持命名覆盖,可以解决此问题,因此您可以执行类似于virtual void LibraryB(void) = LibraryClass::B;的操作。 C#设计团队可能只是忽略了这个问题。


我不认为这是CLR的限制;当使用TypeBuilder和IL Emit构建类型时,您可以指定一个MethodOverride,其中包含一个具有不同名称的方法。这通常用于实现显式接口覆盖,但我相信它将允许您将基类中具有不同名称的方法的覆盖映射。不过,我并没有真正尝试过。 - Dan Bryant
@Dan Bryant:谢谢你的提示,我会尝试并检查一下。 - JPW
5个回答

4

除了继承,编程语言中是否还有其他解决这种情况的方法?

没有。如果你真正觉得这是一个风险,也许可以使用基于接口的设计而不是继承。个人认为,如果您使用比B()更具体的方法名称,这不太可能会给您带来任何重大问题。


你的想法可能是对的。但另一方面,名称冲突可能存在于层次结构中,从一层到另一层,不能使用的名称会累加,很可能会出现相同的常见名称(例如Add,Remove...)。 - JPW

1
如果你想让派生自UserClass的类可以使用LibraryClass中的新方法B,你可以在UserClass中编写如下方法:
public virtual void BNew()
{
    return (this as LibraryClass).B();
}

可能甚至 base.B()就足够了,但我无法再覆盖该方法(调用该方法不是实际的问题)。 - JPW
同意。我意识到你询问的问题比只是“如何调用我已经隐藏了的方法?”要大得多。我的回答是根据事实而做出的,考虑到我们无法改变语言本身,可能是接下来最好的解决方案。即使这有点玄学,但你提出的这个问题确实很有趣。 - FishBasketGordo
说实话:我提出了这个问题是在一场关于语言设计的讲座之后(所以我不能完全自己得到所有的功劳),讲座讨论了这个给定的问题(虽然以较为普遍的方式)。我只是想知道是否有C#中的解决方案,而我不知道。 - JPW

0

你无法完全实现显式接口的行为。

最接近的方法是使用方法隐藏,使用new关键字。

给定这些类,

    public class C1
    {
        public void A()
        {
            Console.WriteLine ("C1 - A");
        }
        public void B()
        {
            Console.WriteLine ("C1 - B");
        }
    }

    public class C2 : C1
    {
        public new void B()
        {
            Console.WriteLine ("C2 - B");
        }
    }

这将会给你这种行为:

        C1 test = new C2 ();
        test.B ();

        C2 test2 = new C2 ();
        test2.B ();

输出:

C1 - B 
C2 - B

我知道这一点,但这并不能解决“原始方法是否可以被覆盖”的问题。 - JPW

0
只要我们谈论在UserClass中更改方法签名为“new”,我们就可以谈论更改方法名称。所以我认为这里没有什么大问题,只需要在VS中点击autorename即可。如果autorename不够用,因为您在其他解决方案中使用这些类,那么您的设计可能有缺陷(即接口)。
这个问题是:
- 非常罕见 - 很容易解决
如果不是这样的话:
- 您的设计很糟糕

只要我拥有所有三个程序集,重命名可能是一个解决方案。但是考虑一个第三方框架(扮演Y的角色),它继承自BCL(扮演X的角色)类和一个客户(Z),该客户在第三方框架中继承自该类。如果框架重命名一个方法,则所有客户(!)都必须更改名称。更糟糕的是:如果签名匹配,则任何覆盖都将被静默重新映射到错误的基类方法,导致意外行为。 - JPW

-1
从某种意义上说,X和Y都有责任:X创建了一个不适合继承的可继承类,并扩展了这样的类,Y创建了一个派生自这样的类。X和Y都无法预测对方将来如何扩展各自的代码。在长期运行中,使用接口(由X完成)或使用包装类(由Y完成)会更少痛苦。

你的意思基本上可以归结为“不要在你无法控制的类上使用继承”(就我理解而言)。这可能是一个实际的答案,但如果X是Microsoft,LibraryClass是System.Windows.Forms.Form,B是一些方法或属性,Microsoft添加它以支持新的Windows版本。现在我几乎束手无策,因为不继承Form并不是一个解决方案。 - JPW
我的意思是从一个不是为继承设计的类派生。System.Windows.Forms.Form不是这样一个类的例子,像System.Collections.Generic.List<T>这样的类更合适。我只是假设LibraryClass不是为继承而设计的类: 除非我们查看X对LibraryClass的文档,否则我们无法知道。 - Peter O.

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