C#: 接口继承中的getter/setter

33

我有一组接口,与特定可变对象密切配合使用。

许多对象的用户只需要从对象中读取值,而且只需要读取其中的几个属性。为了避免名称空间污染(更容易使用智能感知)并传达使用意图,我想拥有一个小的基础接口,它仅以只读方式公开少数“关键”属性。

然而,几乎所有的实现都会支持完整的接口,包括可修改性。

不幸的是,我在用C#表达这个概念时遇到了障碍:

interface IBasicProps {
   public int Priority { get; }
   public string Name {get;}
   //... whatever
}

interface IBasicPropsWriteable:IBasicProps  {
   public int Priority { set; } //warning CS0108: [...] hides inherited member [...]
   public string Name { set; }
   //... whatever
}

我当然没有打算隐藏任何成员,所以这不好!

当然,我可以使用方法来解决问题,但什么是正确的选择呢?即使拆分接口没有其他目的,只是为了传达意图,我也希望保持“核心”接口尽可能小。通过拆分接口,很明显哪些方法不会更新,这使编写代码更加清晰(更不用说还允许漂亮而简单的静态单例存根适用于相当多的简单情况)。

我想避免使用任何抽象类之类的东西;它们使重新实现或快速的单一目的 shim 变得更加复杂和难以理解。

那么,有什么想法吗?

4个回答

33

在接口中进行方法隐藏不会像实现类中那样过于混乱;我会选择以下方式:

interface IBasicProps {
   int Priority { get; }
   string Name {get;}
   //... whatever
}

interface IBasicPropsWriteable:IBasicProps  {
   new int Priority { get; set; }
   new string Name { get; set; }
   //... whatever
}
class Foo : IBasicPropsWriteable {
    public int Priority {get;set;}
    public string Name {get;set;}
/* optional
    int IBasicProps.Priority {get {return Priority;}}
    string IBasicProps.Name {get {return Name;}}
*/
}

作为额外的奖励,这实际上也很适用于协变/逆变;我的实际用例更像是 IBasicProps<out T> {/*...*/ } IBasicPropsWriteable<T>:IBasicProps<T> {}。在这里,接口分离是必要的;一个中央管理器希望使用协变将各种对象放入一个集合中,但每个对象的单独所有者知道它包含的类型。 - Eamon Nerbonne
2
你认为在派生接口中省略新属性的getter,如此处所建议的那样,是怎么想的?https://dev59.com/8HA75IYBdhLWcg3wBkK1#A56cEYcBWogLw_1b0xh9 - Nate Cook
虽然这可能是C#中最不错的解决方案,但使用new修饰符仍然不尽人意,因为它意味着“存在一个无关的同名属性,旧属性仍然存在,但需要更复杂的语法通过IBasicPropsWriteable进行访问”。如果您可以使用额外的访问器扩展“相同”的属性,那将是很好的。(继承类之间也存在同样的问题。)使用您的解决方案,代码static void M(IBasicPropsWriteable x) { var a = x.Name; var b = ((IBasicProps)x).Name; }正式读取两个不同的成员! - Jeppe Stig Nielsen

5

如果你的目标是在阅读和写入时更清晰地区分,那么我建议使用单独的getter和setter方法而不是属性。

interface IBasicProps {
   int GetPriority();
   string GetName();
   //... whatever
}

interface IBasicPropsWriteable:IBasicProps  {
   void SetPriority(int priority);
   void SetName(string name);
   //... whatever
}

3
这不是选美比赛的获胜者,但看起来它应该能很好地工作,这是一个相当可靠的选择。 - Eamon Nerbonne

3

一种方法是简单地跳过接口的继承。创建一个只读接口和一个只写接口,并根据需要实现:

interface IBasicPropsReadable {
   int Priority { get; }
   string Name { get; }
}

interface IBasicPropsWriteable  {
   int Priority { set; }
   string Name { set; }
}

class SomeClassReadWrite : IBasicPropsReadable, IBasicPropsWriteable {
    int Priority { get; set; }
    string Name { get; set; }
}

class SomeClassReadOnly : IBasicPropsReadable {
    int Priority { get; }
    string Name { get; }
}

2
这不能让我表达“读/写对象”的常见概念。现在,我该如何编写一个仅接受读/写变量的方法呢? - Eamon Nerbonne

2

你可以将接口保持无关,只需让你的类实现这两个接口。毕竟,接口只是定义了契约,并且契约不需要相关联。当编码时,让可写的接口从其他接口继承似乎只是一种优化,这样你只需要指定一个接口。

public interface IBasicProps
{
   int Priority { get; }
   string Name {get;}
   //... whatever
}

public interface IBasicPropsWriteable
{
   int Priority { get; set; }
   string Name { get; set; }
   //... whatever
}

public class Foo : IBasicProps, IBasicPropsWriteable
{
   public int Priority { get; set; }
   public string Name { get; set; }

   // whatever
}

如果你真的需要优化,你可以创建另一个从它们两个派生出来的接口,并让你的类实现它。

public interface IBasicPropsAll : IBasicProps, IBasicPropsWriteable  { }

public class Foo : IBasicPropsAll
{
   public int Priority { get; set; }
   public string Name { get; set; }

   // whatever
}

1
IBasicPropsAll方法不太好用。((IBasicPropsAll)new Foo()).Name是有歧义的,编译器无法接受。这意味着你需要经常进行强制转换。 - Eamon Nerbonne
是的。您只需要使用此功能来获得无需键入两个接口的“优化”。 我不推荐这样做。 实际上,您不会使用它,而是在实践中将其转换为IBasicProps或IBasicPropsWriteable之一。 - tvanfosson

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