【问题标题】:Efficiently Obtain IReadOnlyDictionary<int, Animals> from Dictionary<int, Fleas>从 Dictionary<int, Fleas> 中高效获取 IReadOnlyDictionary<int, Animals>
【发布时间】:2019-06-14 07:33:53
【问题描述】:
public class Flea : Animals {...}

var fleas = new Dictionary<int, Flea>();

public IReadOnlyDictionary<string, Animal> Animals => fleas.ToDictionary(pair => pair.Key, pair => (Animal)pair.Value);

Q有没有更高效的方法从fleas获取Animals

【问题讨论】:

  • 一个更大的问题是:你为什么要这样做呢?如果您需要这样做,那么您的应用程序结构可能有问题?
  • 如您所见,您无法投射它。虽然 Flea 继承自 Animals,但基于它们的泛型类不会相互继承。您也许可以将fleas 定义为Dictionay&lt;int, Animals&gt;。然后你可以把跳蚤放进去,你就不需要转换了。
  • 你打算如何使用那本词典? This possibly duplicate question 解释了为什么不能将字典分配给 IDictionary 以及如何 通过委托或简单包装器将项目用作动物
  • This question 解释了为什么与 IDictionary 的协方差不安全以及为什么 IReadOnlyDictionary&lt;TKey, TValue&gt; 也不协变

标签: c# generics casting covariance contravariance


【解决方案1】:

.NET 支持接口、委托、泛型类型和数组的协变。接口或类型必须通过 out 关键字指定它的协变。

你可以写

IEnumerable<Animal> animals=new List<Flea>();

var dict=new Dictionary<int,Flea>{
    [1]=new Flea()
};
IEnumerable<Animal> animals=dict.Values;

这是因为Dictionary.Values 返回一个IEnumerable&lt;Flea&gt;IEnumerable 是协变的——它的定义是IEnumerable&lt;out T&gt;

KeyValuePair 虽然不是协变的,这意味着像 IDictionary&lt;TKey,TValue&gt;IReadOnlyDictionary&lt;TKey,TValue&gt; 这样使用它的类也不是协变的。这是故意的。

由于您只需要从该字典中读取,因此您可以使用委托或在 C# 7 及更高版本中的本地函数来创建访问器方法。您可以将该函数传递给需要 Func&lt;TKey,TValue&gt; 的方法,并使用它从字典中读取值。

如果您有一个需要基于键的访问的方法,比方说:

void Process(Func<int,Animal> reader)
{
    var value=reader(1);
}

在 C# 7 中,您可以编写:

var dict =...

Animal get(int key)=>dict[key];

Process(get);

这有点作弊,通过使用变量捕获来访问字典。

在 C# 7 之前,您会使用委托:

Func<int,Animal> get= key=>dict[key];
Process(get);

这可能看起来很奇怪,但 LINQ 本身就是这样工作的,它使用谓词和委托而不是接口和包装器。

【讨论】:

  • 您关于协方差的示例使我能够简化 my answer 中包装器的 Values 属性。 ?
【解决方案2】:

.NET 框架不包含支持向上转换的字典包装器,但实现一个很简单:

public class ReadOnlyDictionaryUpcast<TKey, TValueDerived, TValueBase>
    : IReadOnlyDictionary<TKey, TValueBase> where TValueDerived : TValueBase
{
    private readonly Dictionary<TKey, TValueDerived> _dictionary;

    public ReadOnlyDictionaryUpcast(Dictionary<TKey, TValueDerived> dictionary)
    {
        _dictionary = dictionary;
    }

    public int Count => _dictionary.Count;

    public TValueBase this[TKey key] => _dictionary[key];

    public bool ContainsKey(TKey key) => _dictionary.ContainsKey(key);

    public bool TryGetValue(TKey key, out TValueBase value)
    {
        bool result = _dictionary.TryGetValue(key, out TValueDerived valueDerived);
        value = valueDerived;
        return result;
    }

    public IEnumerator<KeyValuePair<TKey, TValueBase>> GetEnumerator() => _dictionary
        .Select(e => new KeyValuePair<TKey, TValueBase>(e.Key, e.Value))
        .GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

    public IEnumerable<TKey> Keys => _dictionary.Keys;

    public IEnumerable<TValueBase> Values => 
        (IEnumerable<TValueBase>)(IEnumerable<TValueDerived>)_dictionary.Values;
}

使用示例:

var animals = new ReadOnlyDictionaryUpcast<string, Flea, Animal>(fleas);

【讨论】:

  • 这与 OP 的代码没有什么不同——创建了一个新字典。
  • @PanagiotisKanavos 它是一个包装类,类似于System.Collections.ObjectModel.ReadOnlyDictionary&lt;TKey,TValue&gt;。不涉及副本。
  • 没有必要。 C# 4 及更高版本支持协变和逆变。与动物、跳蚤或鱼(最常见的例子)一起工作可以使用委托或小型包装器as shown in this question
  • @PanagiotisKanavos ReadOnlyDictionaryUpcast 类是一个小型包装器,它执行所需的转换,无需在每次将苍蝇视为动物时调用委托。您提供的link 中提出的解决方案很好,但对我来说似乎不太直观。
  • 这就是 C#、LINQ 和许多其他库的工作方式。对于 .NET Core 性能至关重要的 System.IO.Pipelinse 命名空间和新的 Systrem.Threading.Channels 类也是如此。函数式或专门构建的类,而不是包装器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-22
  • 2016-12-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多