C# 中类的显式接口实现

本文关键字:显式接口实现 | 更新日期: 2023-09-27 18:00:49

是否有与显式接口实现相媲美的东西,但在 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发布了一个新的库版本,并添加了一个虚拟方法B()LibraryClass

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

现在 Y 更新到新的库版本。使用对新版本的引用进行编译时,编译器会发出警告,指出UserClass.B()隐藏了继承的方法LibraryClass.B(),因此应指定 new 关键字或重写该方法。由于现有方法UserClass.B()和新引入的方法之间存在语义差距LibraryClass.B()因此 Y 决定引入 new 关键字,因为任何现有的UserClass.B()重写都可能无法提供DoWork()预期的语义,这会破坏代码。另一方面,Y 希望使用库的新功能,该功能需要覆盖 LibraryClass.B() 。现在这是不可能的:如果覆盖将在派生的 UserClass类中完成,则由于 new 关键字,覆盖将引用UserClass.B();甚至不允许在UserClass本身中覆盖B,因为它已经定义了具有该签名的公共方法。

如果在派生的 UserClass类中有一种方式指定覆盖引用LibraryClass.B()据我所知是不可能的 - 或者 - 如果B()可以在UserClass中显式覆盖:

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# 设计团队可能只是错过了这个问题。

C# 中类的显式接口实现

除了... 语言

中是否有任何方法可以解决这种情况,除了...

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

如果您希望

LibraryClass中的新方法B可用于派生自UserClass的类,则可以用UserClass编写如下方法:

public virtual void BNew()
{
    return (this as LibraryClass).B();
}

您不能将确切的行为作为显式接口实现。您可以获得的最接近的方法是使用方法隐藏,使用 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

只要我们谈论将 UserClass 中的方法签名更改为"新",我们就可以谈论更改方法名称。所以我在这里没有看到什么大问题,只需在 VS 中单击自动重命名即可。如果自动重命名还不够,因为您在解决方案中以外的其他程序集中使用这些类,则设计(即接口(中可能缺少某些内容。

这个问题是:

  • 非常罕见
  • 易于解决

如果不是,则:

  • 你的设计很糟糕
在某种程度上,X 和 Y

都应该受到指责:X 创建了一个不是为继承和扩展此类而设计的可继承类,而 Y 创建了一个派生自此类的类。 X 和 Y 都无法预测对方将来将如何扩展各自的代码。 从长远来看,使用接口(在 X 方面(或使用包装类(在 Y 方面(会不那么痛苦。