C#序列化含未知派生类型的ObservableCollection<ModelRun>问题
在序列化包含插件引入的未知派生类型(如ScenarioModelRun)的ObservableCollection<ModelRun>时,调用XmlSerializer.Serialize报错:
The type ScenarioModelRun may not be used in this context. To use ScenarioModelRun as a parameter, return type, or member of a class or struct, the parameter, return type, or member must be declared as type ScenarioModelRun (it cannot be object). Objects of type ScenarioModelRun may not be used in un-typed collections, such as ArrayLists.
以下是针对该场景的可行解决方案:
方法1:运行时收集派生类型,创建带已知类型的XmlSerializer
由于派生类型由插件动态引入,设计时无法预知,需在运行时收集所有ModelRun的派生类型,再传入XmlSerializer构造函数:
// 遍历当前应用域所有程序集,收集ModelRun的非抽象派生类型 var derivedTypes = AppDomain.CurrentDomain.GetAssemblies() .SelectMany(asm => asm.GetTypes()) .Where(t => t.IsSubclassOf(typeof(ModelRun)) && !t.IsAbstract) .ToArray(); // 创建包含已知派生类型的XmlSerializer XmlSerializer serializer = new XmlSerializer(typeof(ObservableCollection<ModelRun>), derivedTypes); serializer.Serialize(writer, property.GetValue(this));
注意:需确保插件程序集已被加载到当前应用域后再执行类型收集逻辑。
方法2:手动遍历集合元素,按实际类型序列化(推荐)
放弃直接序列化整个集合,改为逐个处理元素,记录每个元素的实际类型,确保派生类型能被正确序列化:
修改WriteXml中的对应逻辑:
if (property.Name == "ModelRuns" || property.Name == "SavedModelRnuns") { writer.WriteStartElement(property.Name); var collection = property.GetValue(this) as IEnumerable<ModelRun>; if (collection != null) { foreach (var item in collection) { if (item == null) continue; var itemType = item.GetType(); writer.WriteStartElement("ModelRun"); // 写入元素实际类型的全名,用于反序列化时识别 writer.WriteAttributeString("type", itemType.FullName); // 为当前元素的实际类型创建XmlSerializer var itemSerializer = new XmlSerializer(itemType); itemSerializer.Serialize(writer, item); writer.WriteEndElement(); } } writer.WriteEndElement(); }
对应的反序列化(ReadXml方法)逻辑:
if (reader.Name == "ModelRuns" || reader.Name == "SavedModelRnuns") { var collection = new ObservableCollection<ModelRun>(); while (reader.Read()) { if (reader.NodeType == XmlNodeType.Element && reader.Name == "ModelRun") { var typeName = reader.GetAttribute("type"); var itemType = Type.GetType(typeName); if (itemType != null) { var itemSerializer = new XmlSerializer(itemType); var item = (ModelRun)itemSerializer.Deserialize(reader.ReadSubtree()); collection.Add(item); } } else if (reader.NodeType == XmlNodeType.EndElement && (reader.Name == "ModelRuns" || reader.Name == "SavedModelRnuns")) { break; } } // 将反序列化后的集合赋值给对应属性 property.SetValue(this, collection); }
方法3:XmlInclude特性(仅适用于已知派生类型)
若提前知晓所有派生类型,可在ModelRun类上添加[XmlInclude]特性,但此方法无法处理插件动态引入的未知类型,仅作补充:
[XmlInclude(typeof(ScenarioModelRun))] public class ModelRun : INotifyPropertyChanged { // 类实现代码 }
内容的提问来源于stack exchange,提问作者FunkyDexter

