AnKing
AnKing

Reputation: 2174

C# typecasting custom object with generics in it

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

Answers (3)

Olivier Jacot-Descombes
Olivier Jacot-Descombes

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

Michael Brown
Michael Brown

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

taquion
taquion

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

Related Questions