接口继承:是否可以扩展属性?

29

我想要做这件事:

interface IBase
{
    string Property1 { get; }
}

interface IInherited : IBase
{
    string Property1 { get; set; }
}

那么,如何让IInherited具有继承的Property1属性并添加允许set的功能呢?

这是否可能?语法是什么?

编辑:请注意我将“inherited”一词加粗。 我特别询问如何继承该属性,而不是在新属性后面隐藏它。


3
我认为这里存在类继承和接口继承的混淆。一个接口并没有像一个类继承另一个类那样真正地继承另一个接口。你的所有代码都在说IInherited也实现了IBase。代码没问题,只是会引发编译器警告,因为你正在更改接口中Property1的签名。你可以使用new来解决这个问题。 - James Gaunt
3
正如许多人所指出的那样,这种语法是不可能的,但我认为它应该是可能的。如果你在Property1上使用反射,将会有一个Property1_get方法和一个Property1_set方法,因此从逻辑上看,你应该能够分别实现它们。而无法这样做的确会导致我有时候需要重复编写代码(或者使用new关键字)。 - cedd
2
@cedd 我也觉得应该这样,因为你提到的同样的原因。属性只是伪装成方法的简化形式。C#当前的设计很奇怪,导致了不必要的混淆,这就是我来到这个SO问题的原因。 - Weipeng
7个回答

28
如果你觉得唯一的方法是使用new关键字,那么在我看来,你对接口的理解是错误的。确实,你可以说IInherited“继承自”IBase,但这究竟意味着什么?这些都是接口,它们建立代码合约。通过用new string Property1 { get; set; }隐藏IBase.Property1属性,你没有掩盖任何功能。因此,许多开发人员认为隐藏是“不好”的传统原因——它违反了多态性,在这种情况下是无关紧要的。
问问自己: 接口真正重要的是什么?它们提供了响应某些方法调用的保证,对吗?
因此,考虑以下两个接口:
interface IBase
{
    string Property1 { get; }
}

interface IInherited : IBase
{
    new string Property1 { set; }
}
  1. 如果一个对象实现了IBase,则可以读取它的Property1属性。
  2. 如果一个对象实现了IInherited,则可以读取它的Property1属性(就像实现IBase一样),并且还可以对其进行写操作。

同样,这里没有什么问题。


5
你的理解是正确的,但在显式实现的情况下会有所不同。每个版本可以有不同的实现方式,因此仍然是隐藏的。实现你接口的人有可能会无意或故意这样做,因此你需要小心地使用这些接口。如果你想避免这个问题,并且仍然想使用继承,那么可以使用“new”,但不要添加第二个getter方法。 - Merlyn Morgan-Graham
1
您的代码为该属性声明了两个不同的getter。在 IInherited 中,您可能只想声明 new string Property1 { set; }。实现 IInherited 接口的类可以具有带有 getter 和 setter 的属性,并且它将实现两个接口。 - Timwi
1
@supercat:这个问题有争议,对吧?我的意思是,就我个人而言,我有点同意你的看法,即担心某人明确实现两个单独的“get”方法,从而“破坏”您的接口设计似乎有些牵强。同时,在某种意义上,确切地定义每个“get”和“set”似乎更加“正确”。所以我不知道。无论如何,Adam的答案为任何感兴趣的人提供了另一种方法。 - Dan Tao
2
问询者是正确的,这是一个问题。例如,使用您的解决方案,这将无法编译:static void M(IInherited x) { var read = x.Property1; } 原因是通过继承链在“稍后”声明的另一个名为 Property1 的属性隐藏了 IBaseProperty1。但该后置属性仅适用于 set。我认为,new 修饰符基本上是错误的,因为它表示我们有一个不相关的属性。它不会“扩展”现有属性。另一种使用 new 属性中的 { get; set; } 的选择也不好,正如 @Timwi 所说。 - Jeppe Stig Nielsen
1
@JeppeStigNielsen 没错,我惊讶于这么多人甚至在不意识到其中的讽刺之处就点赞了这个答案。你对在这里使用“new”语义荒谬的解释完全正确。 - Weipeng
显示剩余7条评论

4
隐藏一个成员违反了Liskov替换原则,基本上不应该这样做。通过隐藏此成员,您将引入一个非常难以定位的错误,因为根据您将对象转换为((IInherited).Property1)还是转换为((IBase).Property1),将会出现两种不同的结果。

http://en.wikipedia.org/wiki/Liskov_substitution_principle


3
这并非传统意义上的隐藏,因为接口不支持多态性。 - Adam Robinson
1
亚当:我认为克里斯是正确的,如果我们将他的评论放在明确实现接口的上下文中,并且有不同的实现。 - Merlyn Morgan-Graham
2
实现 IInherited 接口的类可以被强制转换为 IBase,以获得更少的修改权限。这并不是覆盖方法的实现,因为 getter 方法将返回相同的值(因为两个接口共享 getter 实现)。 - Diego Pereyra
2
@Diego, Chris:如果您显式实现getter,则可以有多个实现。 如果您继承的基类(实现其中一个接口)进行了显式实现,则可能会出现难以找到的错误。 但是,Chris,我建议您删除此内容,因为它会吸引那些不知道更好的人的反对票 =P - Merlyn Morgan-Graham
1
在帖子中提供LSP解释的重要性比我可能因为几个DV而失去的微不足道的分数更有价值,此外,我得到的赞数可以抵消更多的DV。 - Chris Marisic

2

没有明确的方法。你有两个选择:

public interface IBase
{
    string Property1 { get; }
}

public interface IInherited : IBase
{
    void SetProperty1(string value);
}

或者你可以使用new关键字来消除编译器警告:

public interface IBase
{
    string Property1 { get; }
}

public interface IInherited : IBase
{
    new string Property1 { get; set; }
}

除非你显式实现 IInherited.Property1,否则 IBase 将自动绑定到你的可设置实现。


在实现中只有一个名为Property1的属性。如果您通过对IInherited的引用访问实现,则可以调用getter和setter,如果您通过IBase访问它,则只能调用getter。但这不是所需的行为吗? - James Gaunt
是的,那就是我想要的。但是我可能不得不使用getter/setter方法,因为似乎这样做不了... - Malki
可以做到,就像你现在正在做的那样...你是否遇到了编译器错误?错误是什么? - James Gaunt
它编译得很好。但我仍然想要继承。getter/setter方法并不是最糟糕的想法,我只是想使用属性来提高可读性。 - Malki
@Malki:再次强调,接口在继承方面是不存在的。它更像是一种添加方式。你正在使用继承语法,但严格来说,这不是继承。按照你现有的方式操作,并使用new即可。 - Adam Robinson

2

不幸的是 - 属性本身不能被扩展。但是,您可以通过使用 new 来隐藏属性:

interface IInherited : IBase
{
    // The new is actually unnecessary (you get warnings though), hiding is automatic
    new string Property1 { get; set; }
}

或者,您可以创建自己的getter和setter方法,这些方法可以被覆盖(很好的Java风格):

interface IBase
{
    string GetProperty1();
}
interface IInherited : IBase
{
    void SetProperty1(string str);
}

属性实际上是由编译器转换为getter和setter方法。


1

你的代码应该可以正常工作... 只是因为隐藏了 Property1,所以会产生编译器警告。为了消除这个警告,请在 IInherited 中使用新前缀标记 Property1。


0

你可以使用 "new" 关键字标记属性,或者跳过继承:

public interface IBase
{
    string Property1 { get; }
}

public interface IInherited : IBase
{
    new string Property1 { get; set; }
}

或者:

public interface IBase
{
    string Property1 { get; }
}

public interface IInherited
{
    string Property1 { get; set; }
}

无论哪种方式,这都应该可以工作:
public class SomeClass : IInherited, IBase
{
    public string Property1
    {
        get
        {
            // ...
        }
        set
        {
            // ...
        }
    }
}

在为接口构建继承链之前,您可能需要仔细考虑一下。谁会看到哪个接口?当传递IBase时,是否需要转换为IInherited?如果需要,您能保证可以进行此转换吗(如果允许用户创建类,则答案是否定的)?如果不小心使用这种继承方式,可能会对可重用性造成很大伤害。

0

我在工作中与几位主要开发人员进行了这样的对话,我们得出的结论是:

  • 从技术上讲,无论哪种方式都可以。
  • 接口都是关于单一目的的。

接口用于定义任何继承类可以执行的方法。即使您的第二个接口共享某些元素,或者甚至在某些相同的条件下使用,接口的定义也要讲解如何在与该接口相关的所有条件下使用类。

此外,出于这个原因,类能够继承多个接口。就代码清晰度而言:

public class SomeClass : IInherited, IBase

上面的代码明确说明了SomeClass能够执行IInherited和IBase的操作,而其中一些操作是相同的并不重要。如果必须通过接口向后查找以发现IInherited扩展了IBase,可能会使未来其他开发人员混淆你的代码。
看起来在两个接口中拥有相同的值是重复的劳动,但是你的代码功能没有做出任何假设。

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