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)
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);
}
}