Search code examples
c#.net-4.0generic-variance

Versatile solution for adding object of a type to an object of fitting generic type


I feel like interface (contra?)variance is the answer, but cannot find the right solution.

Let us have these classes:

public abstract class Fruit { }
public class Banana : Fruit { }
public class Apple : Fruit { }

public abstract class Picture { }
public class FruitPicture<T> : Picture, Contravariant<T>, Covariant<T> where T : Fruit
{
    T myFruit = null;

    public Type GetFruitType() { return typeof(T); }

    public void AddFruit(T fruit) { this.myFruit = fruit; }
}

public interface Contravariant<in T> { void AddFruit(T model); }
public interface Covariant<out T> { Type GetFruitType(); }

My situation being:

  • I have a collection of Bananas and Apples already initialized, such as these two (but I can use a different one):

    Fruit[] myFruits = new Fruit[2]
    {
        new Banana(),
        new Apple()
    };
    
  • I have a collection of Pictures, such as these two:

    Picture[] myPictures = new Picture[2]
    {
        new FruitPicture<Banana>(),
        new FruitPicture<Apple>(),
    };
    

Now, I seek to do a very simple thing, but in a versatile manner, meaning I want to avoid any switches/ifs where I would have to change code each time a new fruit is found and new FruitPicture may appear in the collection => I want to .AddFruit() from my collection to the proper type of FruitPicture. I can change pretty much any of the logic, but I want to keep the generic FruitPicture class.

Closest I got would be:

foreach(Picture curPicture in myPictures)
{
    foreach (Fruit curFruit in myFruits)
    {
        Covariant<Fruit> fruitType = (Covariant<Fruit>)curPicture;
        if (curFruit.GetType() == fruitType.GetFruitType())
        {
            // what now?
        }
    }
}

Thank you mr. Skeet (joking; sort of)


Solution

  • Since the issue you have is you want to do compile-time type-safety but you don't know the types until run-time, you can push the decision off until run-time by using dynamic. I am not necessarily recommending this, just saying it will work.

    I changed the Covariant interface to not need be generic since it made no use of the type parameter. I renamed AddFruit to SetFruit since it didn't add anything, but replaced.

    foreach (var fruit in myFruits) {
        foreach (var picture in myPictures) {
            if (picture is Covariant cov) {
                if (cov.GetFruitType() == fruit.GetType())
                    ((dynamic)picture).SetFruit(Convert.ChangeType((dynamic)fruit, cov.GetFruitType()));
            }
        }
    }
    

    The (dynamic) ChangeType is needed since the type of fruit is Fruit, which is not a valid type to pass to any SetFruit. It has to be dynamic since the static compile-time type of ChangeType is object, which is also not a valid type for any SetFruit.

    Alternatively, what if you pushed the decision into the FruitPicture?

    public interface Covariant {
        void SetCompatibleFruit(Fruit f);
    }
    
    public class FruitPicture<T> : Picture, Covariant where T : Fruit {
        T myFruit = null;
    
        public void SetCompatibleFruit(Fruit f) {
            if (f is T tf)
                this.myFruit = tf;
        }
    }
    

    Then just ask each Covariant picture to set the fruit if it can:

    foreach (var fruit in myFruits) {
        foreach (var picture in myPictures) {
            if (picture is Covariant cov)
                cov.SetCompatibleFruit(fruit);
        }
    }