Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# cast Dictionary<string, AnyType> to Dictionary<string, Object> (Involving Reflection)

Is it possible to cast a Dictionary<string, Anything> to a consistent intermediate generic type? So I would be able to cast <string, string>, <string, bool>, <string, int>, <string, anything> all to the same type of dictionary?

I am working on a project that is using heavy reflection and I need to be able to process DIctionary types as such:

FieldInfo field = this.GetType().GetField(fieldName);
Dictionary<string, Object> dict = (Dictionary<string, Object>)field.GetValue(this);

The above code is what I currently have and the program always fails at the cast from field.GetValue to the generic Dictionary<string, Object>.

Is there a way to do this? Or should I just figure out a different way to process these Dictionaries?

Any help would be greatly appreciated.

like image 821
Ryan Sullivan Avatar asked Apr 18 '12 09:04

Ryan Sullivan


5 Answers

Explanation of What's Wrong: Variance

As stated in this answer:

it's not true that a Dictionary<string, bool> is a Dictionary<string,object>

Let me explain why. It all has to do with covariance and contravariance in C#. In summary, neither the key type K nor the value type V in Dictionary<K, V> is either purely an input parameter or an output parameter across the entire dictionary. So neither generic type can be cast to a weaker or a stronger type.

If you cast to a weaker type, then you break inputs. For example the Add function which expects types K, V or stronger cannot accept supertypes of either of K, V. If you cast to a stronger type, you break outputs. For example, the indexer property returns a type V. How can we cast this safely to a sub-type of V, knowing only that the original type is V? We can't.

A Type-Safe Partial Solution Using Variant Generics

There is a way to allow strongly-typed casting, but only on partial fragments of the original dictionary interface which are consistent in terms of covariance/contravariance of generic parameters. This solution uses a wrapper type which implements a bunch of partial interfaces. Each partial interface has a specific type of combination of co/contra-variance of the key/value type. Then we combine all these partial interfaces into a master interface and implement that through a backing object which is a regular dictionary. Here goes. First, the interfaces:

public interface IDictionaryBase
{
    int Count { get; }
    bool IsReadOnly { get; }
    void Clear();
}

public interface IInKeyInValueSemiDictionary<in K, in V> : IDictionaryBase, IInKeySemiDictionary<K>
{
    V this[K key] { set; }
    void Add(K key, V value);
}

public interface IInKeyOutValueSemiDictionary<in K, out V> : IDictionaryBase, IInKeySemiDictionary<K>, IOutValueSemiDictionary<V>
{
    V this[K key] { get; }
    ISuccessTuple<V> TryGetValue(K key);
}

public interface ISuccessTuple<out V>
{
    bool WasSuccessful { get; }
    V Value { get; }
}

public class SuccessTupleImpl<V> : ISuccessTuple<V>
{
    public bool WasSuccessful { get; }
    public V Value {get;}

    public SuccessTupleImpl(bool wasSuccessful, V value)
    {
        WasSuccessful = wasSuccessful;
        Value = value;
    }
}

public interface IInKeySemiDictionary<in K> : IDictionaryBase
{
    bool ContainsKey(K key);
    bool Remove(K key);
}

public interface IOutKeySemiDictionary<out K> : IDictionaryBase
{
    IEnumerable<K> Keys { get; }
}

public interface IOutValueSemiDictionary<out V> : IDictionaryBase
{
    IEnumerable<V> Values { get; }
}

Note: we don't have to cover all combinations of in/out here for the generic parameters, and also note that some of the interfaces only need a single generic parameter.The reason is some combinations of covariance/contravariance don't have any associated methods, so there's not need for corresponding types. And note that I'm leaving out the KeyValuePair<K, V> type- you'd need to do a similar trick on this if you wanted to include it, and it doesn't seem worth it at this stage.

So, next, the union of all those interfaces:

public interface IVariantDictionary<K, V> : IInKeyInValueSemiDictionary<K, V>, IInKeyOutValueSemiDictionary<K, V>,
    IOutKeySemiDictionary<K>, IDictionary<K, V>
{ }

And then, the wrapper class:

class VariantDictionaryImpl<K, V> : IVariantDictionary<K, V>
{
    private readonly IDictionary<K, V> _backingDictionary;

    public VariantDictionaryImpl(IDictionary<K, V> backingDictionary)
    {
        _backingDictionary = backingDictionary ?? throw new ArgumentNullException(nameof(backingDictionary));
    }

    public V this[K key] { set => _backingDictionary[key] = value; }

    V IInKeyOutValueSemiDictionary<K, V>.this[K key] => _backingDictionary[key];

    V IDictionary<K, V>.this[K key] { get => _backingDictionary[key]; set => _backingDictionary[key] = value; }

    public int Count => _backingDictionary.Count;

    public bool IsReadOnly => _backingDictionary.IsReadOnly;

    public IEnumerable<K> Keys => _backingDictionary.Keys;

    public ICollection<V> Values => _backingDictionary.Values;

    ICollection<K> IDictionary<K, V>.Keys => _backingDictionary.Keys;

    IEnumerable<V> IOutValueSemiDictionary<V>.Values => Values;

    public void Add(K key, V value)
    {
        _backingDictionary.Add(key, value);
    }

    public void Add(KeyValuePair<K, V> item)
    {
        _backingDictionary.Add(item);
    }

    public void Clear()
    {
        _backingDictionary.Clear();
    }

    public bool Contains(KeyValuePair<K, V> item)
    {
        return _backingDictionary.Contains(item);
    }

    public bool ContainsKey(K key)
    {
        return _backingDictionary.ContainsKey(key);
    }

    public void CopyTo(KeyValuePair<K, V>[] array, int arrayIndex)
    {
        _backingDictionary.CopyTo(array, arrayIndex);
    }

    public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
    {
        return _backingDictionary.GetEnumerator();
    }

    public bool Remove(K key)
    {
        return _backingDictionary.Remove(key);
    }

    public bool Remove(KeyValuePair<K, V> item)
    {
        return _backingDictionary.Remove(item);
    }

    public ISuccessTuple<V> TryGetValue(K key)
    {
        bool wasSuccessful = _backingDictionary.TryGetValue(key, out V v);
        return new SuccessTupleImpl<V>(wasSuccessful, v);
    }

    public bool TryGetValue(K key, out V value)
    {
        return _backingDictionary.TryGetValue(key, out value);
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)_backingDictionary).GetEnumerator();
    }
}

Once you wrap a dictionary in this class, you can then use it either as a regular dictionary, or as any of the covariant/contravariant fragment interface types defined.

like image 89
Colm Bhandal Avatar answered Sep 22 '22 15:09

Colm Bhandal


When I stumbled onto the same situation, I created the following helper:

/// <summary>
/// Casts a dictionary object to the desired Dictionary type.
/// </summary>
/// <typeparam name="TKey">The target Key type.</typeparam>
/// <typeparam name="TValue">The target value type.</typeparam>
/// <param name="dictionary">The dictionary to cast.</param>
/// <returns>A copy of the input dictionary, casted to the provided types.</returns>
private Dictionary<TKey, TValue> CastDictionary<TKey, TValue>(IDictionary dictionary)
{
    // Get the dictionary's type.
    var dictionaryType = typeof(Dictionary<TKey, TValue>);

    // If the input is not a dictionary.
    if (dictionaryType.IsAssignableFrom(typeof(Dictionary<,>)))
    {
        // Throw an exception.
        throw new Exception("The cast to a dictionary failed: The input object is not a dictionary.");
    }

    // Get the generic arguments of the dictionary.
    var arguments = dictionaryType.GetGenericArguments();

    // If the first type of the dictionary is not a descendant of TKey.
    if (!(arguments[0] is TKey || arguments[0].IsAssignableFrom(typeof(TKey)))
        // Or its second type is not a descendant of TValue.
        || !(arguments[1] is TValue || arguments[1].IsAssignableFrom(typeof(TValue))))
    {
        // Throw an exception.
        throw new Exception("The cast to a dictionary failed: The input dictionary's signature does not match <" + typeof(TKey).Name + ", " + typeof(TValue).Name + ">");
    }

    // Get the dictionary's default constructor.
    var constructor = dictionaryType.GetConstructor(Type.EmptyTypes);

    // Create a new dictionary.
    var output = (Dictionary<TKey, TValue>)constructor.Invoke(null);

    // Loop through the dictionary's entries.
    foreach (DictionaryEntry entry in dictionary)
    {
        // Insert the entries.
        output.Add((TKey)entry.Key, (TValue)entry.Value);
    }

    // Return the result.
    return output;
}

Could be used in your case as follows:

FieldInfo field = (IDictionary)this.GetType().GetField(fieldName);

Dictionary<string, Object> dict = CastDictionary<string, Object>(field.GetValue(this));
like image 30
alozzi Avatar answered Oct 18 '22 04:10

alozzi


Following AakashM's answer, the Cast doesn't seem to play ball. You can get around it by using a little helper method though:

IDictionary dictionary = (IDictionary)field.GetValue(this);
Dictionary<string, object> newDictionary = CastDict(dictionary)
                                           .ToDictionary(entry => (string)entry.Key,
                                                         entry => entry.Value);

private IEnumerable<DictionaryEntry> CastDict(IDictionary dictionary)
{
    foreach (DictionaryEntry entry in dictionary)
    {
        yield return entry;
    }
}

The duck typing in foreach is handy in this instance.

like image 18
Gibsnag Avatar answered Oct 18 '22 06:10

Gibsnag


Is this helping you ?

Dictionary<a, b> output =
   input.ToDictionary(item => item.Key, item => (SomeType)item.Value);
like image 14
Royi Namir Avatar answered Oct 18 '22 04:10

Royi Namir


Even if you could find some way to express this, it would be the wrong thing to do - it's not true that a Dictionary<string, bool> is a Dictionary<string, object>, so we definitely don't want to cast. Consider that if we could cast, we could try and put a string in as a value, which obviously doesn't fit!

What we can do, however, is cast to the non-generic IDictionary (which all Dictionary<,>s implement), then use that to construct a new Dictionary<string, object> with the same values:

FieldInfo field = this.GetType().GetField(fieldName);
IDictionary dictionary = (IDictionary)field.GetValue(this);
Dictionary<string, object> newDictionary = 
    dictionary
    .Cast<dynamic>()
    .ToDictionary(entry => (string)entry.Key,
                  entry => entry.Value);

(note that you can't use .Cast<DictionaryEntry> here for the reasons discussed here. If you're pre-C# 4, and so don't have dynamic, you'll have to do the enumeration manually, as Gibsnag's answer does)

like image 10
AakashM Avatar answered Oct 18 '22 05:10

AakashM