有没有办法以*限制*行为的方式对 C# 接口进行子类化?

Is there any way to subclass a C# interface in a way that *restricts* behavior?

public interface IBlackBox<T> {
  bool IsValid();
  T GetValue();
}

public abstract class Box<T>:IBlackBox<T> {
  public bool IsValid() {
    return true;
  }
  public abstract T GetValue();
}

A Box 按需产生一个值;可以是按需计算,或者随机,或者只是一个奇特的可为空等。 BlackBox 做同样的事情,除了它可能会过期或以其他方式变得无效。 Boxes 需要适合 BlackBox 列表(并且强调不是相反),这决定了继承层次结构。

我不喜欢的是 Box 是抽象的 class 而不是接口。这引起了两个令人头疼的问题。首先,这意味着我无法导出 struct,在 Box 仅包含静态值的情况下,这将是非常可取的。

其次,这意味着我无法再class 任何其他内容。例如,一个预期用途是 Dictionary<string,BlackBox<int>> 本身就是 Box 并按需求和其值。没有充分的理由不应该只是 subclass Dictionary,至少有一个很好的理由应该是:不要逐行包装 IDictionary 接口!但是有些地方需要Box有效性保证,好像只能有一个。

可以 有一个 interface IBox<T> : IBlackBox<T> 是空的,仅用作 "trust me" 标签。这意味着在实现 IBox<T> 的每个 class 中复制 Box 的单行 IsValid(),但这比手动包装 IDictionary 麻烦得多。不过,我希望有一个更具体的解决方案。

这是我想出的最佳解决方案。这只是骨架,但我提供了完整的实现(使用更好的名称)on GitHub

public interface IBlackBox
public interface IBlackBox<T> : IBlackBox {
  Func<T> GetValue;
}

public interface IBox { }
public interface IBox<T> : IBlackBox<T>, IBox { }

public interface IWrappedBox { }
public interface IWrappedBox<T> : IBox<IBlackBox<T>>, IWrappedBox { }

public interface IEmptyBox { }
public static class EmptyBox {
  public static EmptyBox<T> Get<T>() {
    return EmptyBox<T>.value;
  }
  sealed class EmptyBox<T> : IBlackBox<T>, IEmptyBox {
    public static readonly EmptyBox<T> value = new EmptyBox<T>();
    public Func<T> GetValue {
      get {
        throw new NotSupportedException
          ("Cannot extract a value from an empty box.");
      }
    }
  }
}

非通用接口允许在类型级别进行测试,例如:

IBlackBox something = someWrappedBox.GetValue();
if (something is IEmptyBox)
  HandleEmpty();
else
  HandleSomething(something.GetValue());

这仍然需要结合编译器无法检查的三个规则,但结构使其易于符合:

  • IBlackBox<T>IBlackBoxIEmptyBox不能直接继承。
  • 只有IWrappedBox<T>.GetValue()可以return基地IBlackBox<T>.
  • Return 值不能具体为任何 IEmptyBox.

IEmptyBox 保证只在明确允许的情况下出现; IBox<T>保证包含好的T;凡是可以继承的都是接口。任务完成!

我建议制定一个 IValidBox<T> : IBlackBox<T> 合同,其中规定没有 合法 实施可能 IsValid return 错误,并且消费者有权调用 GetValue 而无需检查 IsValid。此外,为了减少样板文件,您可以定义一个实现 IValidBox<T> 的抽象 class ValidBoxBase<T>,这样不需要继承任何其他内容的 IValidBox<T> 的实现可以节省一些样板。在这个简短的例子中并不多,但在更充实的场景中可能会很重要。