【问题标题】:Compile-time restrictions: Allow As but not Bs where B:A编译时限制:允许 As 但不允许 Bs where B:A
【发布时间】:2010-12-06 17:00:20
【问题描述】:

在下面的层次结构中,有没有办法在编译时排除 IBars 同时仍然允许 IFoos?

例子:

//IFoo defines functionality that Snafu needs to use, and so is a type restriction.
public interface IFoo {...}

//IBar defines supplemental required functionality that Snafu does not support.
public interface IBar:IFoo {...}

//Can I generate a compiler error if an IBar is used as the generic type?
public class Snafu<T> where T:IFoo
{
    public void DoSomethingWith(T myFoo)
    {
        //Best thing I can think of with the current hierarchy
        if(myFoo is IBar) throw new ArgumentException("IBars are not supported");
    }
}

我能想到的唯一可行的方法是定义第三个“标志”接口,并将其应用于所有不是 IBar 的 IFoo。定义了 IFoo 和 IBar:

public interface IAmNotAnIBar:IFoo {}

public class Snafu<T> where T:IAmNotAnIBar {...}

但是,这闻起来像 hack;新接口没有定义任何 IFoo 没有定义的新接口,开发人员必须知道不要使用 IFoo(IIRC 你不能通过将 IFoo 设为内部来隐藏它,同时仍将其他接口公开),所以他们可以只需实现 IFoo,就可以善意或恶意地绕过编译时检查。还有什么更优雅的吗?

编辑:感谢所有到目前为止的回复。也许一个更具体的例子可以说明我为什么首先提出这个问题。

假设 IFoo 定义了映射到 DB 的域对象的基本功能。它必须具有读写 ID 属性,也许能够发射和吸收 DTO,等等。系统中有几个区域将这些对象作为 IFoo 处理,包括持久性: IFoo 使用可以处理任何 IFoo 的存储库(上例中的类 Snafu;DoSomething() 执行一些持久性操作)被读取和写入持久性存储在强类型 IFoo 上)。

现在,让我们将 IBar 定义为与 IFoos 不同的域对象。除了 IFoo 功能之外,它们还定义了一些额外的 Initialize() 方法,以确保它们处于一致的状态。它们仍然是域对象 (IFoos),并且在系统的所有区域中仍应如此对待,除非您不能将 IBar 传递给 Snafu 的实例并期望得到正确的结果,因为 Snafu 不会也不能正确调用该方法(假设 Initialize() 接受一个参数,该参数是 Snafu 没有也不应该给出的外部依赖项)。相反,您应该调用一个不同的类,它是所有 IBar 的存储库。我的目标是在编译时提醒开发人员他们做错了什么,而不是依赖于对 Snafu 上的每个可能的 DoSomething() 调用进行充分的运行时测试(单元、集成、功能、手动等)以确保它永远不会通过 IBar。

【问题讨论】:

  • IFooonly 允许的类型吗?或者是否允许IBar 以外的后代?
  • 如果 IBar 扩展 IFoo,它应该做 IFoo 所做的所有事情,以及其他事情。如果 IBar 不能用于需要 IFoo 的地方,则 IBar 不应该真正扩展 IFoo。 Liskov substitution principle
  • @dtb:我同意第一和第二部分,但第一部分并不一定意味着第二部分。见编辑; IBar 完成了 IFoo 所做的一切,但也公开了必须在特定情况下使用的附加功能。因此,在这种情况下,IBar 不能用作 IFoo(是的,这违反了 LSP)。
  • 根据您的编辑,我会说 IFoo 和 IBar 是不同的东西,尽管它们共享一些功能并且可以与某些操作一起使用。所以 IFoo 和 IBar 都应该扩展 IFooBarBase,这样在两者都可以接受的情况下,两者都可以使用,而当操作仅对此有意义时,只能使用两者中的一个。
  • 你所谓的“标志接口”实际上可以是一个非常好的模式。考虑接口IReadableMatrixIImmutableMatrixIChangeableMatrixIImmutableMatrix 不能“做”任何 IReadableMatrix 不能做的事情,但可以做出前者做不到的承诺。请注意,虽然有些人可能会争辩 ImmutableMatrix 应该是一个类,但如果愿意信任实现者,将其作为接口是有好处的。除此之外,如果未来的应用程序需要存储内容符合某些以前无法预料的模式的不可变矩阵,则有可能......

标签: c# generics types


【解决方案1】:

如果您在允许IFoos 的同时尝试排除IBars,这告诉我IBar 不应真正继承自IFoo(它们不是真正的父-> 子关系)。

请记住,仅仅因为两个接口共享同名成员并不意味着一个应该从另一个继承。如果你像这样使用继承,你给从父级继承的成员赋予了新的含义,从而违反了里氏替换原则。在您的情况下,很明显 IBar 也不是 IFoo...否则您不需要限制它们的使用。

我的猜测是有一种方法可以重构你的界面,让事情变得更有意义,但没有更多细节,我很难说。

【讨论】:

    【解决方案2】:

    这是一个奇怪的设计。不知道为什么你不想允许这样做,但既然你想要它。

    你可以让 IFoo 不实现 IBar,然后让实现类实现两者。如果 IBar 具有与 IBar 相同的方法很重要,只需将它们复制到 IFoo 中即可满足实现类。

    【讨论】:

      【解决方案3】:

      假设您想禁止任何派生类,这是没有意义的。然后你基本上是说only IFoo 是允许的,在这种情况下不需要泛型类。

      【讨论】:

        【解决方案4】:

        那么where T:IFoo 不正确。考虑将您的接口定义分解为更小的合约,然后将Snafu&lt;T&gt; 约束T 仅用于那些实际支持的接口。

        【讨论】:

          【解决方案5】:

          这应该是不可能的,因为 IBar 一个 IFoo。

          如果您担心从您的类派生的人可能会改变某些行为,不妨考虑使用 sealed 类。

          【讨论】:

            【解决方案6】:

            IBar 是否继承自 IFoo 无关紧要。在编译时无法指定对象不能支持接口。您最好的选择可能是拥有任何不支持您的接口的类的密封版本,并让它们支持如下接口(使用 VB 语法显示以避免 HTMLish 修改尖括号):

            Interface ISelf(Out T As Class)
              函数 Self() 作为 T
            端接口
            接口INonBar(Out T)
              继承 ISelf(Out T As 类)
            结束接口

            注意,我建议定义一个单独的 ISelf 接口,然后可以被其他接口继承,以避免多个接口都定义一个“Self”方法。想要接受不实现 Bar 的 Foo 的方法将接受 INonBar(of Foo); 类型的参数。可以使用 Self 方法将其有效地重铸为 Foo。

            顺便说一句,这种方法也有一个限制,即无法指定一个字段是实现两个不相关接口的对象,或者是某个类的派生类,该类实现了该类本身未实现的接口。后一种规范可以在函数参数中实现,但有一个烦人的限制,即无法将参数存储在字段或其他对象中,以便它们可以被检索并传递给类似的函数(没有反射,甚至不可能对已知满足两种类型约束的字段进行类型转换,以便将其传递给要求参数满足两种类型约束的函数。

            顺便说一句,虽然您正在寻找的特定模式是不寻常的,但在其他情况下,拥有不遵循常规继承模式的对象很有用。例如,对象 Foo 可能派生 Bar,而后者又派生出 Boz。 “Foo”类型的对象可以被克隆而不会损坏,“Bar”类型的对象也可以,但“Boz”类型的对象不能。如上所述定义 ISelf(T) 和 ICloneable(Of T),可以定义密封类型 CloneableFoo 和 CloneableBar,并有一个可以接受其中任何一个的例程(通过让它接受 ICloneable(Of Foo) 类型的对象 [not ICloneable(的 CloneableFoo)!] 模式有点难看,但总比让 Foo 支持 .Clone 和让 Boz.Clone 抛出 NotSupportedException 要好。

            【讨论】:

              猜你喜欢
              • 2018-10-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-01-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多