Liskov替换原理和接口

Pat*_*rik 2 c# liskov-substitution-principle interface

ICollection<T>.Add()数组的实现是否违反Liskov替换原则?该方法导致a NotSupportedException,它确实打破了LSP,恕我直言.

string[] data = new string[] {"a"};
ICollection<string> dataCollection = data;
dataCollection.Add("b");
Run Code Online (Sandbox Code Playgroud)

这导致了

未处理的异常:System.NotSupportedException:Collection具有固定大小.

我找到了一个关于 - Stream实现的非常相似的问题.我打开一个单独的问题,因为这个案例非常不同:Liskov替换原则和Streams.这里的不同之处在于,ICollection它不像-class那样提供CanAdd-Property或类似的东西Stream.

Lua*_*aan 6

我明白你为什么这么认为.有一个函数需要一个集合,它期望它是可修改的.传递数组会使它失败,所以显然你不能用这个特定的实现替换接口,对吗?

这是个问题吗?也许.这取决于你期望理想持有的频率.你是不是偶然会使用一个阵列而不是一个集合,然后在十年之后惊讶它会崩溃?并不是的..NET应用程序使用的类型系统并不完美 - 它没有告诉您这种特殊ICollection<T>用法要求集合可以修改.

如果数组没有假装实现ICollection<T>(或者IEnumerable<T>它们也没有"真正"实现),.NET会更好吗?我不这么认为.有没有办法保持数组"正在"的便利性ICollection<T>,这也可以避免相同的LSP违规?不.底层数组仍然是固定长度的 - 充其量,你会违反更多有用的原则(比如预期引用类型不具有引用透明性的事实).

可是等等!让我们来看看实际的合同ICollection<T>.Add.是否允许NotSupportedException扔掉?哦是的 - 引用MSDN:

[如果......则抛出NotSupportedException] ICollection是只读的.

查询时,数组会返回true IsReadOnly.合同得到维护.

如果你考虑Stream不破坏LSP CanWrite,你必须考虑数组是有效的集合,因为它们有IsReadOnly,而且它是true.如果函数接受只读集合并尝试添加它,则该函数中存在错误.没有办法在C#/ .NET中明确指定它,所以你必须依赖合同的其他部分而不仅仅是类型 - 例如,函数的文档应该指定为集合抛出一个NotSupportedException(ArgumentException或者其他)只读.一个好的实现将在函数开始时进行此测试.

需要注意的一件重要事情是,C#中的类型并不像定义LSP的类型理论那样受到限制.例如,您可以在C#中编写这样的函数:

bool IsFrob(object bobicator)
{
  return ((Bob)bobicator).IsFrob;
}
Run Code Online (Sandbox Code Playgroud)

可以bobicator用任何超类型代替object?显然不是.但它显然不是穷人Frobinate类型的问题- 这是IsFrob函数中的错误.在实践中,C#(和大多数其他语言)中的许多代码仅适用于比方法签名中的类型所指示的约束更多的对象.

如果对象违反了其超类型的合同,则该对象仅违反LSP.它不能对其他代码违反LSP 负责.通常你会发现在LSP下完全没有完全支持的代码是非常务实的- 工程是,而且一直都是关于权衡.仔细权衡成本.