Reputation: 283173
The specific exception I'm getting is:
Unable to cast object of type
NbtByte
to typeINbtTag<System.Object>
On this line:
tag = (INbtTag<object>)new NbtByte(stream);
Where tag
is declared as:
INbtTag<object> tag;
And NbtByte
is defined as:
public class NbtByte : INbtTag<byte>
Where IBtTag
is:
public interface INbtTag<out T>
I thought by declaring it as out T
I would be able to do things like this.
Basically, I want to have a dictionary of IbtTag<T>
s,
var dict = new Dictionary<string, INbtTag<object>>();
but where T
is of different types (thus I declared it with object
). Is this possible?
Upvotes: 2
Views: 195
Reputation: 36092
One of the limiting aspects of using generic types is that generic types are not as flexible in type conversions as traditional types. A List<Object>
is not assignment compatible with a List<String>
or any other object type.
There are conversion helper functions in Linq such as .Cast<T>()
that will logically cast every element from one list to the T type to form a new list of type List<T>
. While the helper function is handy, it doesn't change the fact that List<N>
is not type compatible with List<T>
even if N and T are type compatible in some fashion.
Essentially, generic types aren't polymorphic. There is no common type between instances of the same generic type - no type you can declare a variable with that can hold a List<T>
and a List<N>
.
If you are creating your own generic type and you do want to have some common type that can be used to carry around all manifestations of the generic type, you have to perform some type gymnastics to get it to work, and you will be limited in what you can do. You need to define a base class or interface type, and then you need to make your generic type inherit from the base class or implement the interface type. With this configuration, you can declare a variable with the base class type or interface type, and can assign MyClass<N>
and MyClass<T>
to the same variable, accessing only the members defined in the base class (which will of course not have any knowledge of the N or T type params).
Covariant type params ( IMyInterface<out T>
) may get you part of the way there but covariance places severe restrictions on what you can do in that interface - the covariant type T can only be used for function results, never for arguments or settable properties.
The simple fact that IList<T>
is not declared covariant is not an accident or oversight - it can't be covariant. To be useful IList<T>
needs methods like Add(T item) and Remove(T item) which are forbidden when T is covariant.
Upvotes: 1
Reputation: 126932
Interface variance applies only to reference types. Value types (such as ints, bytes, etc., as well as custom structs) are excluded. For example, you cannot use an array of integers as IEnumerable<object>
even though the array is IEnumerable<int>
.
IEnumerable<object> objs = new int[] { 1, 2, 3 }; // illegal
IEnumerable<object> objs = new string[] { "a", "b", "c" }; // legal
To solve your issue with the dictionary, you can perhaps elect to define a non-generic interface. (Where your generic interface might expose members as type T, the non-generic interface would simply expose object
.)
Say you have
interface INbtTag { } // non-generic interface
interface INbtTag<out T> : INbtTag { } // covariant generic interface
Then you could use your dictionary as Dictionary<string, INbtTag>
.
The drawback is that when you implement the interface, you have to implement both. This usually means implementing the generic version implicitly, and the non-generic explicitly. For example:
interface INbtTag
{
object GetValue();
}
interface INbtTag<out T> : INbtTag
{
T GetValue();
}
class NbtByte : INbtTag<byte>
{
byte value;
public byte GetValue() // implicit implementation of generic interface
{
return value;
}
object INbtTag.GetValue() // explicit implementation of non-generic interface
{
return this.GetValue(); // delegates to method above
}
}
Upvotes: 7