如何在C#中使用派生类作为泛型类型实现类型转换?
泛型Tree结构的协变兼容解决方案
问题分析
你遇到的核心问题是泛型类Node<T>是不变类型,无法直接将Node<DerivedData>隐式转换为Node<BaseData>——因为Node<T>同时包含输入型成员(Parent属性的setter)和输出型成员(Data、Children属性的getter),而C#中只有接口/委托支持协变/逆变,类本身不支持。
尝试直接用协变接口INodeOut<T>虽然能解决单个节点的转换,但Tree<T>依赖Node<T>的Parent可写属性,协变接口无法暴露setter(会破坏协变规则),导致无法完成父子关系绑定。
解决方案
通过拆分只读协变接口和可写逆变接口,配合调整Tree<T>的泛型约束,实现类型安全的协变兼容:
1. 定义拆分的接口
// 协变接口:仅暴露只读输出成员,支持将Node<Derived>转换为INodeOut<Base> public interface INodeOut<out T> { INodeOut<T>? Parent { get; } Tree<T> Children { get; } T? Data { get; } } // 逆变接口:仅暴露可写输入成员,支持给Node<Derived>设置INodeOut<Base>类型的Parent public interface IEditableNode<in T> { INodeOut<T>? Parent { set; } } // 合并接口:供Node类实现,同时支持协变转换和编辑操作 public interface INode<T> : INodeOut<T>, IEditableNode<T> { }
2. 调整Node类实现
public class Node<T> : INode<T> { private INodeOut<T>? _parent; public readonly Tree<T> Children; public T? Data { get; set; } // 实现协变接口的只读Parent INodeOut<T>? INodeOut<T>.Parent => _parent; // 实现逆变接口的可写Parent INodeOut<T>? IEditableNode<T>.Parent { set => _parent = value; } public Node() => Children = new(this); }
3. 重构Tree类支持协变节点
public class Tree<T> : List<INodeOut<T>> { protected INodeOut<T>? _owner; public Tree() { } public Tree(INodeOut<T>? owner) => _owner = owner; // 重载Add方法,接受任意派生类型的Node public void Add<U>(Node<U> node) where U : T { base.Add(node); // 通过逆变接口设置父节点 if (node is IEditableNode<T> editableNode) { editableNode.Parent = _owner; } } // 支持直接添加INodeOut<T>类型的节点 public new void Add(INodeOut<T> node) { base.Add(node); if (node is IEditableNode<T> editableNode) { editableNode.Parent = _owner; } } }
4. 最终使用示例
public class Program { public Program() { // 直接添加Node<DerivedData>到Tree<BaseData>,编译通过 Tree<BaseData> tree = new() { new Node<DerivedData>() }; // 协变转换单个节点后添加 INodeOut<BaseData> node = new Node<DerivedData>(); tree.Add(node); } }
关键说明
- 协变接口
INodeOut<out T>确保Node<Derived>可以安全转换为INodeOut<Base>,因为它只暴露只读的输出成员; - 逆变接口
IEditableNode<in T>允许给Node<Derived>设置INodeOut<Base>类型的父节点,符合逆变的输入规则; Tree<T>存储INodeOut<T>类型,同时通过重载Add方法支持直接添加派生类型的Node<U>,并通过接口转换完成父子关系绑定,兼顾类型安全和协变需求。
内容的提问来源于stack exchange,提问作者Omar Vargas
相关产品推荐
相关产品推荐

