Adolfo Perez
Adolfo Perez

Reputation: 2874

How to cast Dictionary<string,MyClass> to Dictionary<string,object>

I have the following classes:

public class BagA : Dictionary<string, BagB>
{}
public class BagB : Dictionary<string, object>
{}

Now, through reflection I'm creating an object of type BagB which I'm trying to add to an object I created of type BagA:

object MyBagA // Created through reflection
object MyBagB // Created through reflection

((Dictionary<string,object>)MyBagA).Add("123",MyBagB);  //This doesnt work

Gives me the following error: Unable to cast object of type 'BagA' to type 'System.Collections.Generic.Dictionary`2[System.String,System.Object]'.

Why can't I cast a Dictionary<string, BagB> to Dictionary<string, object>? Which is the best way to add my Item based on this scenario? perhaps Anonymous methods..?

Notice that I would prefer not having to modify my classes BagA and BagB...

Thanks!

Upvotes: 4

Views: 5006

Answers (4)

KeithS
KeithS

Reputation: 71591

You cannot directly cast or convert an instance of a generic type to an instance of the type with different generic parameters, UNLESS the generic type is specifically defined as covariant (able to be treated as a generic of a base class of the actual declared generic type) on that particular generic type parameter, AND you are attempting to cast the type to a generic of a base class of its actual type. For instance, an IEnumerable<string> can be treated as an IEnumerable<object> because string derives from object. It cannot be treated as an IEnumerable<char> even if all the strings only have one character, because String does not derive from Char.

Covariance is definable in C# 4.0 using the out keyword on the generic parameter, but to my knowledge, unlike IEnumerable, the generic IDictionary interface is not specified as covariant. In addition, even though a Dictionary is IEnumerable, it's an IEnumerable of the generic key/value pairs, and generic KVPs are not covariant, so you cannot treat the KVP's generic parameters as base types.

What you can do is create a new Dictionary of the new type and transfer all the values from the old one. If those values are reference types, changing a sub-value of one Dictionary's reference-typed Value will change it in the corresponding Value of the other Dictionary (unless you change the reference itself, by assigning a new instance of MyClass to the Value for that key).

A little Linq makes this one pretty easy:

Dictionary<string, MyClass> MyStronglyTypedDictionary = 
   new Dictionary<string, MyClass>();
//populate MyStronglyTypedDictionary

//a Dictionary<T1, T2> is an IEnumerable<KeyValuePair<T1, T2>>
//so most basic Linq methods will work
Dictionary<string, object> MyGeneralDictionary = 
   MyStronglyTypedDictionary.ToDictionary(kvp=>kvp.Key, kvp=>(object)(kvp.Value));

...

//Now, changing a MyClass instance's data values in one Dictionary will 
//update the other Dictionary
((MyClass)MyGeneralDictionary["Key1"]).MyProperty = "Something else";

if(MyStronglyTypedDictionary["Key1"].MyProperty == "Something else")
{
    //the above is true; this code will execute
}

//But changing a MyClass reference to a completely new instance will
//NOT change the original Dictionary
MyGeneralDictionary["Key1"] = new MyClass{MyProperty = "Something new"};

if(MyStronglyTypedDictionary["Key1"].MyProperty == "Something else")
{
    //the above is STILL true even though the instance under this key in the 
    //other Dictionary has a different value for the property, because
    //the other dictionary now points to a different instance of MyClass;
    //the instance that this Dictionary refers to never changed.
}

Upvotes: 1

JaredPar
JaredPar

Reputation: 755587

There is no way to do a cast here because Dictionary<string, BagB> and Dictionary<string, object> are different incompatible types. Instead of casting the Dictionary why not cast the values instead?

MyBagA.Add("123", (BagB)MyBagB);

If it were legal to cast the Dictionary<TKey, TValue> then then very evil things could happen. Consider

Dictionary<string, BagB> map1 = ...;
Dictionary<string, object> map2 = SomeEvilCast(map1);
map2["foo"] = new object();

What would now happen if I tried to access map1["foo"]? The type of the value is object but it's statically typed to BagB.

Upvotes: 4

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 727137

Since you used reflection to create your objects, it's only fair that you need to continue using it to call their methods:

var addMethod = typeof(BagA).GetMethod("Add", new[] {typeof(string), typeof(BagB)});
addMethod.Invoke(MyBagA, new object[] {"123", MyBagB});

Upvotes: 2

SLaks
SLaks

Reputation: 888293

This is fundamentally impossible.

Had you been able to do that, you would be able to add arbitrary other types.

You need to call the method using reflection.

Upvotes: 2

Related Questions