3
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 Is there a more efficient way to obtain Animals from fleas?

participant
  • 2,923
  • 2
  • 23
  • 40
  • 2
    A bigger question is: why would you want to do this anyway? If you are needing to do it, then perhaps you have an issue with your app structure? – DavidG Jun 14 '19 at 07:46
  • As you've discovered, you can't cast it. While Flea inherits from Animals, generic classes based on them do not inherit from each other. You might be able to define `fleas` as `Dictionay` instead. Then you could put fleas into it and you wouldn't need to convert. – Hans Kilian Jun 14 '19 at 08:03
  • How do you intend to use that dictionary? [This possibly duplicate question](https://stackoverflow.com/questions/12841458/idictionary-contravariance) explains why you can't assign a dictionary to an IDictionary and how you *can* use the items as animals, either through delegates or a simple wrapper – Panagiotis Kanavos Jun 14 '19 at 10:02
  • Possible duplicate of [IDictionary<,> contravariance?](https://stackoverflow.com/questions/12841458/idictionary-contravariance) – Panagiotis Kanavos Jun 14 '19 at 10:02
  • [This question](https://stackoverflow.com/questions/2149589/idictionarytkey-tvalue-in-net-4-not-covariant) explains why covariance with `IDictionary` isn't safe and why `IReadOnlyDictionary` isn't covariant either – Panagiotis Kanavos Jun 14 '19 at 10:13
  • @PanagiotisKanavos I agree with you that the covariance of `IDictionary<,>` is unsafe, but the covariance of `IReadOnlyDictionary<,>` would be safe but is currently infeasible due to implementation details. But still, this does not answer how to efficiently obtain an `IReadOnlyDictionary`. – participant Jun 14 '19 at 10:32
  • @participant it's `KeyValuePair` that causes the problem, not IReadOnlyDictionary. That type is used in both interfaces so it can't be covariant in one case but not another. This *does* answer the question - what do you want to do with it? Whatever you want to do, you can do with another mechanism – Panagiotis Kanavos Jun 14 '19 at 10:41
  • @participant `Dictionary.Values` for example returns an `IEnumerable` which is covariant. You can use accessor defined as delegates, extension methods or local methods to read items by key. Or you can use a `reader` wrapper that exposes just the methods you need – Panagiotis Kanavos Jun 14 '19 at 10:44

2 Answers2

3

.NET supports covariance in interfaces, delegates, generic types and arrays. The interface or type has to specify it's covariant though with the out keyword.

You can write

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

or

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

This works because Dictionary.Values returns an IEnumerable<Flea> and IEnumerable is covariant - its definition is IEnumerable<out T>.

KeyValuePair though isn't covariant which means that the classes that use it like IDictionary<TKey,TValue> and IReadOnlyDictionary<TKey,TValue> aren't either. This was intentional.

Since you only need to read from that dictionary, you can create an accessor method using a delegate or, in C# 7 and later, a local function. You can pass that function to methods that expect a Func<TKey,TValue> and use it to read values from the dictionary.

If you have a method that needs key-based access, let's say :

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

In C# 7 you can write :

var dict =...

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

Process(get);

This cheats a bit, by using variable capture to access the dictionary.

Before C# 7 you'd use a delegate :

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

This may seem strange, but that's how LINQ itself works, by using predicates and delegates instead of interfaces and wrappers.

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Your example about covariance allowed me to simplify the `Values` property of the wrapper in [my answer](https://stackoverflow.com/a/56595603/11178549). – Theodor Zoulias Jun 14 '19 at 11:23
1

The .NET framework does not contain a dictionary wrapper that supports upcasting, but implementing one is trivial:

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;
}

Usage example:

var animals = new ReadOnlyDictionaryUpcast<string, Flea, Animal>(fleas);
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • This is no different than the OP's code - a new dictionary gets created. – Panagiotis Kanavos Jun 14 '19 at 09:50
  • @PanagiotisKanavos it is a wrapper class, similar to [`System.Collections.ObjectModel.ReadOnlyDictionary`](https://learn.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.readonlydictionary-2). There is no copy involved. – Theodor Zoulias Jun 14 '19 at 09:56
  • There's no need for it. C# 4 and later support covariance and contravariance. Working with animals, fleas or fishes (the most common example) can be done using either delegates or a tiny wrapper [as shown in this question](https://stackoverflow.com/questions/12841458/idictionary-contravariance) – Panagiotis Kanavos Jun 14 '19 at 10:01
  • @PanagiotisKanavos the class `ReadOnlyDictionaryUpcast` is a small wrapper that does the required casting, removing the need to call a delegate every time you want to treat a fly as an animal. The solutions proposed in the [link](https://stackoverflow.com/questions/12841458/idictionary-contravariance) you provided are fine, but seem less intuitive to me. – Theodor Zoulias Jun 14 '19 at 10:17
  • That's how C#, LINQ and a lot of other libraries work though. The same holds for the `System.IO.Pipelinse` namespace that's critical to .NET Core performance and the new `Systrem.Threading.Channels` classes. Functional or purpose-built classes instead of wrappers. – Panagiotis Kanavos Jun 14 '19 at 10:34
  • @PanagiotisKanavos The .NET framework already contains at least two wrapper containers: [ReadOnlyCollection](https://learn.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.readonlycollection-1), [ReadOnlyDictionary](https://learn.microsoft.com/en-us/dotnet/api/system.collections.objectmodel.readonlydictionary-2). Is there any recommendation about not creating new ones? – Theodor Zoulias Jun 14 '19 at 10:41
  • I just found that an almost identical implementation has already been posted here: [How to get around lack of covariance with IReadOnlyDictionary?](https://stackoverflow.com/a/13602918/11178549) – Theodor Zoulias Jun 14 '19 at 14:23