如何优雅实现Animal派生类的强类型字典集合并优化性能?
我有三个类:
AnimalGiraffe : AnimalTiger : Animal
想要实现三个字典类:
public abstract class AnimalDictionary { internal Dictionary<int, Animal> _internalDictionary; //TODO: Dictionary interface implementation, minus the "Add" method } public class GiraffeDictionary : AnimalDictionary, IDictionary<int, Giraffe> { //Collection Code Examples public List<Giraffe> GetGiraffesWithHerdID(int herdID); public void FleeFromTigers(TigerDictionary pride); } public class TigerDictionary : AnimalDictionary, IDictionary<int, Tiger> { //Collection Code Examples public List<Tiger> GetActiveHunters(); public void HuntGiraffeHerd(GiraffeDictionary targetHerd); }
我清楚基类AnimalDictionary不能实现IDictionary或ICollection,否则会出现把Tiger放进GiraffeDictionary的类型安全问题——我虽然没完全搞懂协变与逆变,但明白不能让老虎进长颈鹿围栏的道理。
于是我让AnimalDictionary继承IEnumerable<KeyValuePair<int, Animal>>,实现除Add()外的所有字典方法,Add()交给派生类实现。
但在派生集合类实现IDictionary时,写出了不优雅的代码:
ICollection<Giraffe> IDictionary<int, Giraffe>.Values => _internalDictionary.Values.Cast<Giraffe>().ToList();
这让我很担心,因为需要在密集循环里遍历这些集合。我希望遍历Values集合时避免类型转换和创建新列表的性能开销,但Values是ICollection而非IEnumerable。
请问该怎么解决这个问题?有没有我忽略的替代方案?还是应该放弃基类,接受派生类里存在重复方法?
1. 封装类型安全的ICollection实现,避免ToList()开销
你可以自行实现一个ICollection<T>包装类,直接对_internalDictionary.Values做类型转换包装,而非每次创建新列表。这样遍历仅在访问元素时做一次类型转换,不会额外生成集合副本。
示例代码:
internal class TypedCollectionWrapper<T> : ICollection<T> where T : Animal { private readonly ICollection<Animal> _source; public TypedCollectionWrapper(ICollection<Animal> source) { _source = source; } public int Count => _source.Count; public bool IsReadOnly => _source.IsReadOnly; public void Add(T item) => throw new NotSupportedException("Values collection is read-only"); public void Clear() => throw new NotSupportedException("Values collection is read-only"); public bool Contains(T item) => _source.Contains(item); public void CopyTo(T[] array, int arrayIndex) { for (int i = 0; i < _source.Count; i++) { array[arrayIndex + i] = (T)_source.ElementAt(i); } } public IEnumerator<T> GetEnumerator() => _source.Cast<T>().GetEnumerator(); public bool Remove(T item) => throw new NotSupportedException("Values collection is read-only"); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); }
派生类中这样实现Values属性:
ICollection<Giraffe> IDictionary<int, Giraffe>.Values => new TypedCollectionWrapper<Giraffe>(_internalDictionary.Values);
2. 改用泛型基类,从根源避免类型转换
放弃原AnimalDictionary抽象类,改用泛型基类AnimalDictionary<T>(T : Animal)。基类直接持有Dictionary<int, T>,派生类无需类型转换,同时保证类型安全。
示例代码:
public abstract class AnimalDictionary<T> : IDictionary<int, T> where T : Animal { protected Dictionary<int, T> _internalDictionary = new Dictionary<int, T>(); // 实现IDictionary<int, T>通用方法 public bool ContainsKey(int key) => _internalDictionary.ContainsKey(key); public bool Remove(int key) => _internalDictionary.Remove(key); public bool TryGetValue(int key, out T value) => _internalDictionary.TryGetValue(key, out value); public T this[int key] { get => _internalDictionary[key]; set => _internalDictionary[key] = value; } public ICollection<int> Keys => _internalDictionary.Keys; public ICollection<T> Values => _internalDictionary.Values; // Add方法留给派生类实现(如需控制添加逻辑) public abstract void Add(int key, T value); } public class GiraffeDictionary : AnimalDictionary<Giraffe> { public override void Add(int key, Giraffe value) { // 长颈鹿特有验证逻辑 _internalDictionary.Add(key, value); } public List<Giraffe> GetGiraffesWithHerdID(int herdID) { return _internalDictionary.Values.Where(g => g.HerdID == herdID).ToList(); } public void FleeFromTigers(TigerDictionary pride) { // 业务逻辑实现 } } public class TigerDictionary : AnimalDictionary<Tiger> { public override void Add(int key, Tiger value) { // 老虎特有验证逻辑 _internalDictionary.Add(key, value); } public List<Tiger> GetActiveHunters() { return _internalDictionary.Values.Where(t => t.IsActiveHunter).ToList(); } public void HuntGiraffeHerd(GiraffeDictionary targetHerd) { // 业务逻辑实现 } }
该方案从根源消除类型转换需求,性能最优,同时保留代码复用性——基类实现通用字典方法,派生类仅处理特有逻辑。
3. 妥协方案(不推荐):显式实现IEnumerable
若不想修改基类结构,可在派生类将IDictionary<int, T>的Values属性显式实现为IEnumerable<T>,但这违反接口规范,仅适用于大部分场景仅需遍历Values的情况,可能导致调用方出错。
总结
优先推荐泛型基类方案,既保证类型安全,又消除性能开销,代码结构更清晰。若无法修改基类结构,则使用TypedCollectionWrapper包装类,既能满足接口要求,又避免不必要的集合复制。
内容的提问来源于stack exchange,提问作者Quasar

