列表继承更改破坏了向后兼容性
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,则该方法可能会抛出异常或根本不存在。
最近我 运行 在一个定义为
的支持库中 classpublic 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,则该方法可能会抛出异常或根本不存在。