列表继承更改破坏了向后兼容性

List Inheritance Change Breaks Backwards Compatibility

最近我 运行 在一个定义为

的支持库中 class

public class CustomCollection<T> : ObservableCollection<T>

在查看代码时,我意识到从列表中扩展对可用性和性能更好:

public class CustomCollection<T> : List<T>

在我们的实现中,实际上并没有使用任何特定于 ObservableCollection 的内容,因此我认为此更改将“适用于”所有消费者。当我更新主应用程序以使用 CustomCollection 从 List 扩展的新库时,我 运行 遇到以下异常:

Collection is read only

这让我感到惊讶,因为在主应用程序中,我能够毫无问题地执行标准 myCustomCollection.Add(someItem)。只有当代码通过另一个(未重新编译的)库执行时才会导致问题,例如:

+ Main application
  - Direct ussage  of CustomCollection = Success
  - Call to library that was not recompiled
    -- Usage for CustomCollection fails on .Add(...) with read-only error

有没有人知道为什么会发生这种情况? ObservableCollection 扩展自 Collection,它实际上在内部由 List<T> 支持,所以我不确定现在正在执行什么代码路径导致只读错误。

你可以简化场景。创建控制台应用程序:

static void Main(string[] args)
{
    Debugger.Launch();

    var c = new CustomCollection<string>();
    c.Add("Foo");
}

并创建一个您从控制台应用程序引用的 class 库,其中只有一行:

public class CustomCollection<T> : System.Collections.ObjectModel.ObservableCollection<T> { }

运行 在 c.Add() 上设置断点的应用程序,然后按 F10 验证程序。现在重现错误:

  • System.Collections.ObjectModel.ObservableCollection更改为System.Collections.Generic.List
  • 构建class库(右击项目,select构建)。
  • 转到输出目录并将 DLL 复制到控制台应用程序的可执行文件的目录中,覆盖它编译时所针对的旧目录。
  • 现在 运行 双击可执行文件,系统将询问您是否要在已经 运行ning 的 Visual Studio.[=45 实例中进行调试=]
  • 附加到程序并按 F5 继续,现在将抛出您的异常。

在 .NET Core 3.1 上,上述步骤导致:

Exception of type 'System.ExecutionEngineException' was thrown.

看看 IL,原因就很清楚了:

// [20 7 - 20 71]
IL_0012: newobj       instance void class [ClassLibWithCollections]ClassLibWithCollections.CustomCollection`1<string>::.ctor()
IL_0017: stloc.0      // V_0
IL_0018: ldloc.0      // V_0
IL_0019: ldstr        "Foo"
IL_001e: callvirt     instance void class [System.Runtime]System.Collections.ObjectModel.Collection`1<string>::Add(!0/*string*/)
IL_0023: nop

它被编译为对 Collection<T>.Add(T) 的虚拟调用,但是 List<T> 派生的 CustomCollection<T> 而不是 继承自 Collection<T>,所以 运行 时间抛出该异常。

要解决这个问题,您必须编译这种类型的用户,因为您已经进行了重大更改。

当您像这样更改基类型时,也可能会抛出其他异常。重点是:调用转到基 class(或 class 的基 classes 之一),如果您更改基 class,则该方法可能会抛出异常或根本不存在。