How to Get Around Lack of Covariance with Ireadonlydictionary

How to get around lack of covariance with IReadOnlyDictionary?

You could write your own read-only wrapper for the dictionary, e.g.:

public class ReadOnlyDictionaryWrapper<TKey, TValue, TReadOnlyValue> : IReadOnlyDictionary<TKey, TReadOnlyValue> where TValue : TReadOnlyValue
{
private IDictionary<TKey, TValue> _dictionary;

public ReadOnlyDictionaryWrapper(IDictionary<TKey, TValue> dictionary)
{
if (dictionary == null) throw new ArgumentNullException("dictionary");
_dictionary = dictionary;
}
public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); }

public IEnumerable<TKey> Keys { get { return _dictionary.Keys; } }

public bool TryGetValue(TKey key, out TReadOnlyValue value)
{
TValue v;
var result = _dictionary.TryGetValue(key, out v);
value = v;
return result;
}

public IEnumerable<TReadOnlyValue> Values { get { return _dictionary.Values.Cast<TReadOnlyValue>(); } }

public TReadOnlyValue this[TKey key] { get { return _dictionary[key]; } }

public int Count { get { return _dictionary.Count; } }

public IEnumerator<KeyValuePair<TKey, TReadOnlyValue>> GetEnumerator()
{
return _dictionary
.Select(x => new KeyValuePair<TKey, TReadOnlyValue>(x.Key, x.Value))
.GetEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}

IReadOnlyDictionary lack of covariance

In my particular case this helped:

interface IRoomTarget
{
IBoxTarget this[string name] { get; }
}

interface IRoomSource
{
IBoxSource this[string name] { get; }
}

class Room : IRoomTarget, IRoomSource
{
Dictionary<string, Box> boxes = new Dictionary<string, Box>();

public IBoxTarget IRoomTarget.this[string name]
{
get { return boxes[name]; }
}

IBoxSource IRoomSource.this[string name]
{
get { return boxes[name]; }
}
}

But I'm not sure that's universal solution

IDictionaryTKey, TValue in .NET 4 not covariant

It's a feature. .NET 4.0 only supports safe covariance. The cast you mentioned is potentially dangerous as you could add a non-string element to the dictionary if that was possible:

IDictionary<string, object> myDict = new Dictionary<string, string>();
myDict["hello"] = 5; // not an string

On the other hand, IEnumerable<T> is a read-only interface. The T type parameter is only in its output positions (return type of the Current property) so it's safe to treat IEnumerable<string> as an IEnumerable<object>.

C#: How to implement an interface with an IReadOnly dictionary containing interface values from a concrete dictionary containing concrete values

You have to create a new object, but you don't have to create a copy of the original dictionary. Instead, you can create a view over the original dictionary, with the new shape. Here's some entirely-untested-but-simple code that I believe should work:

public class ReadOnlyDictionaryWrapper<TKey, TValue, TOriginalValue> : IReadOnlyDictionary<TKey, TValue>
where TOriginalValue : TValue
{
private readonly IReadOnlyDictionary<TKey, TOriginalValue> original;

public ReadOnlyDictionaryWrapper(IReadOnlyDictionary<TKey, TOriginalValue> original) =>
this.original = original;

public TValue this[TKey key] => original[key];

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

public IEnumerable<TValue> Values => original.Values.Cast<TValue>();

public int Count => original.Count;

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

public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() =>
original.Select(pair => new KeyValuePair<TKey, TValue>(pair.Key, pair.Value))
.GetEnumerator();

public bool TryGetValue(TKey key, out TValue value)
{
bool ret = original.TryGetValue(key, out var originalValue);
value = originalValue;
return ret;
}

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

I'd suggest creating a single instance of this in your Configuration constructor, to wrap _values.

How to get around lack of covariance with IReadOnlyDictionary?

You could write your own read-only wrapper for the dictionary, e.g.:

public class ReadOnlyDictionaryWrapper<TKey, TValue, TReadOnlyValue> : IReadOnlyDictionary<TKey, TReadOnlyValue> where TValue : TReadOnlyValue
{
private IDictionary<TKey, TValue> _dictionary;

public ReadOnlyDictionaryWrapper(IDictionary<TKey, TValue> dictionary)
{
if (dictionary == null) throw new ArgumentNullException("dictionary");
_dictionary = dictionary;
}
public bool ContainsKey(TKey key) { return _dictionary.ContainsKey(key); }

public IEnumerable<TKey> Keys { get { return _dictionary.Keys; } }

public bool TryGetValue(TKey key, out TReadOnlyValue value)
{
TValue v;
var result = _dictionary.TryGetValue(key, out v);
value = v;
return result;
}

public IEnumerable<TReadOnlyValue> Values { get { return _dictionary.Values.Cast<TReadOnlyValue>(); } }

public TReadOnlyValue this[TKey key] { get { return _dictionary[key]; } }

public int Count { get { return _dictionary.Count; } }

public IEnumerator<KeyValuePair<TKey, TReadOnlyValue>> GetEnumerator()
{
return _dictionary
.Select(x => new KeyValuePair<TKey, TReadOnlyValue>(x.Key, x.Value))
.GetEnumerator();
}

System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}

Confusion about covariance

You have at least two misconceptions here - even before you reach your question - which is conflating the problem.

Generic type parameter names

The first issue has nothing to do with variance. Understand that this:

public override IDictionary<uint, NetworkNode> 
hydrateNodes<NetworkNode>(string nodesTableName)
{
IDictionary<uint, NetworkNode> returnDict =
new Dictionary<uint, PowerNetworkNode>();
}

Is exactly equivalent to this:

public override IDictionary<uint, NetworkNode> 
hydrateNodes<T>(string nodesTableName)
{
IDictionary<uint, T> returnDict =
new Dictionary<uint, PowerNetworkNode>();
}

That is, the generic type parameter in the method name does not refer to an actual type, it just refers to what you are going to call that type in the method. If you call a method public void DoSomething<SomeClass>, this has nothing to do with a class called SomeClass.

It should be clear from the above that you can't assign a Dictionary<uint, PowerNetworkNode> to a IDictionary<uint, T> - you have no idea whether a PowerNetworkNode is a T.

However, if you look a little more closely, you'll realise that this method doesn't need to be generic at all - you always want T to be a NetworkNode, so just...

public override IDictionary<uint, NetworkNode> 
hydrateNodes(string nodesTableName)
{
IDictionary<uint, NetworkNode> returnDict =
new Dictionary<uint, PowerNetworkNode>();
}

This is a step in the right direction but it leads to your next problem.

Variance

As you know, IDictionary<TKey, TValue> is invariant in TValue, so you can't assign a Dictionary<uint, PowerNetworkNode> to a IDictionary<uint, NetworkNode>.

Now, you've mentioned in the comments that you want "a key/value pair type that is covariant in its value."

Bad news - you can't have one of those. It's inherently a contradiction. If you declare something as Something<NetworkNode> where you can add a NetworkNode, the compiler expects you to be able to add any NetworkNode to it. If you assign a Something<PowerNetworkNode> to that - you've broken that contract. That's why invariance exists: because it is a logical necessity.

The good news is that you shouldn't need to - you can put a PowerNetworkNode in an IDictionary<uint, NetworkNode> - because a PowerNetworkNode is a NetworkNode.

So (unless you're doing something very strange in the rest of the method), the following should be fine:

public override IDictionary<uint, NetworkNode> 
hydrateNodes(string nodesTableName)
{
IDictionary<uint, NetworkNode> returnDict =
new Dictionary<uint, NetworkNode>();
}

Hopefully this has provided some insight.

Want to avoid returning writable references inside of a complex read-only class (game state)

Since FactionsMgr.Faction is mutable, let's suppose that it has writable properties like this:

class Faction {
public int Foo { get; set; }
public string Bar { get; set; }
public float Baz { get; set; }
public SomeOtherMutableThing AnotherThing { get; set; }
}

You should create a corresponding read only interface for it, and make Faction implement it:

interface IReadOnlyFaction {
// exclude all members that can change Faction's state
int Foo { get; }
string Bar { get; }
float Baz { get; }
IReadOnlySomeOtherMutableThing AnotherThing { get; }
}

interface IReadOnlySomeOtherMutableThing {
// do the same thing there...
}

class Faction: IReadOnlyFaction {
public int Foo { get; set; }
public string Bar { get; set; }
public float Baz { get; set; }
public SomeOtherMutableThing AnotherThing { get; set; }

// you need an explicit interface implementation here, unless you are using C# 9
IReadOnlySomeOtherMutableThing IReadOnlyFaction.AnotherThing => AnotherThing;
}

Then, you can declare public members in State as of type IReadOnlyFaction, and delegate them to a private member of type Faction. The private member is also what the Command class will modify.

private Faction someFaction;
public IReadOnlyFaction SomeFaction => someFaction;

That is the general case. However, if you have collections of these mutable types, like your dictionary of _unit_map, you would need to do a bit more work.

You would still have a public read only member and a private mutable member, but the delegating process is less straightforward. You would need a wrapper.

private Dictionary<Vector2, FactionsMgr.Faction> _unit_map = new();

public IReadOnlyDictionary<Vector2, IReadOnlyFaction> UnitMap;

// ...

// in the constructor of State...

// using the ReadOnlyDictionaryWrapper class from the linked answer
UnitMap = new ReadOnlyDictionaryWrapper<Vector2, FactionsMgr.Faction, IReadOnlyFaction>(_unit_map);


Related Topics



Leave a reply



Submit