Reputation: 2174
I have the follwoing:
subObject, superObject where subObject is a subclass of superObject.
I can always upcast subObject to superObject, but i cannot do the following:
wrapperObject<superObject> instance = (wrapperObject<superObject>) wrraperObject<subObject> instance2;
This is an example with generic lists:
List<Object> l1 = null;
List<Boolean> l2 = null;
l1 = (Object)l2; <<< This will not work
Object o1 = null;
Boolean o2 = false;
o1 = (Object)o2; <<< This works
I understand that in case with lists I can just iterate thru all the objects within the list and typecast them individually. But this will not work in case of my custom class "wrapperObject"
wrapperObject<superObject> l1;
wrapperObject<subObject> l2;
l1 = (wrapperObject<superObject>)l2; <<< this doesnt work
superObject a = null;
subObject n = null;
a = (superObject)n; <<< this works
Upvotes: 1
Views: 551
Reputation: 112437
Let us assume for a moment that this would be possible
List<bool> lb = new List<bool>();
List<object> lo = (List<object>)lb;
Now, we could do
lo.Add(123); // Since lo is typed as List<object>
but lo
is just a reference pointing to List<Boolean> lb
. Bang!
A workaround for this type of problem is to have a base type (class or interface) that is not generic and derive a generic one from it. For instance, List<T>
implements ICollection<T>
which implements IEnumerable<T>
which implements IEnumerable
. I.e., this assignment is valid:
IEnumerable e = new List<bool>();
Note that you can do this type of conversion with arrays. I.e., you can assign
object[] obj = new Person[10];
The price we must pay for this is efficiency, since a type test is performed when we assign an array element. This type test can throw an exception if we assign a non-compatible value. See: Eric Lippert's Blog on Array Covariance
Upvotes: 4
Reputation: 9153
What you're asking about are Variant Generics. Right now, C# only allows Variant Generics on Interfaces and only in one direction. The two types of generic variance are covariance where the output of a function can be more precise than the declared variant type or contravariance where the input of a function can be less precise than the declared variant type.
If your interface needs to do both on the same variable, you're out of luck.
Upvotes: 0
Reputation: 2767
Oliver's answer is totally right, it explains why you simply can not do this. But I can think of an instructive workaround that besides helping you to achive what you want, it will help you to better understand Covariance and Contravariance in .Net. Consider the following classes:
class SuperObject { }
class SubObject : SuperObject { }
class WrapperObject<T>:IContravariantInterface<T>,ICovariantInterface<T> where T : SuperObject
{
public void DoSomeWork(T obj)
{
//todo
}
public T GetSomeData()
{
//todo
return default;
}
}
We make Wrapper implementing two interfaces: IContravariantInterface<T>
and ICovariantInterface<T>
. Here are they:
interface IContravariantInterface<in T> where T : SuperObject
{
void DoSomeWork(T obj);
}
interface ICovariantInterface<out T> where T : SuperObject
{
T GetSomeData();
}
By doing this we split up Wrapper functionality into two parts: a covariant one and a contravariant. Why doing this? Because by doing this we can safetly cast either from most derive classes to less ones or the other way around with the condition that we are using the right interface:
var superObjectWrapper = new WrapperObject<SuperObject>();
var subObjectWrapper = new WrapperObject<SubObject>();
ICovariantInterface<SuperObject> covariantSuperObjWrapper = subObjectWrapper;
IContravariantInterface<SuperObject> contravariantSuperObjWrapper = subObjectWrapper; //does not compile
ICovariantInterface<SubObject> covariantSubObjWrapper = superObjectWrapper; //does not compile
IContravariantInterface<SubObject> contravariantSubObjWrapper = superObjectWrapper;
By casting to these interfaces you are sure that you can only access those methods that are safe to use regarding your casting
EDIT
Base on OP's comment below consider writing converter logic in your Wrapper class. Take a look to the following refactored WrapperObject:
class WrapperObject<T> where T : SuperObject
{
private T _justATestField;
public void Copy<TType>(WrapperObject<TType> wrapper) where TType : SuperObject
{
if (wrapper._justATestField is T tField)
{
_justATestField = tField;
}
}
public WrapperObject<SuperObject> GetBaseWrapper()
{
var baseWrapper = new WrapperObject<SuperObject>();
baseWrapper.Copy(this);
return baseWrapper;
}
}
Now you can do:
var subObjectWrapper = new WrapperObject<SubObject>();
WrapperObject<SuperObject> superObjectWrapper = subObjectWrapper.GetBaseWrapper();
Upvotes: 1