How to correctly cast a class to an abstract class when using type generics?
Asked Answered
H

4

25

I have the following classes

public abstract class BaseViewPresenter { }
public abstract class BaseView<T> : UserControl
    where T : BaseViewPresenter { }

public class LoginPresenter : BaseViewPresenter { }
public partial class LoginView : BaseView<LoginPresenter> {  }

I have a method that looks like this (simplified)

public BaseView<BaseViewPresenter> Resolve(BaseViewPresenter model)
{
    var type = model.GetType();
    var viewType = _dataTemplates[type];

    // Correctly creates BaseView object
    var control = Activator.CreateInstance(viewType);

    // Fails to cast as BaseView<BaseViewPresenter> so returns null
    return control as BaseView<BaseViewPresenter>;
}

When I call this using an instances of LoginPresenter

var login = new LoginPresenter();
var ctl = Resolve(login);

The line Activator.CreateInstance(viewType) correctly resolves into a new instances of my LoginView, however control as BaseView<BaseViewPresenter> can't do the cast correctly so returns null.

Is there a way to correctly cast the control into BaseView<BaseViewPresenter> without using specific type generics?

Since LoginView inherits from BaseView<LoginPresenter>, and LoginPresenter inherits from BaseViewPresenter, I would assume there's a way to convert LoginView to BaseView<BaseViewPresenter>.

I am stuck with using .Net 3.5

Holomorphic answered 11/9, 2014 at 16:24 Comment(8)
Could you use in interface instead of an abstract class and mark T as covariant? Otherwise this kind of cast is not allowed.Indecorous
@Indecorous No I can't, I'm stuck with using .Net 3.5Holomorphic
You cannot correctly cast the control into BaseView<BaseViewPresenter> because it is a BaseView<LoginPresenter>Integumentary
You might be able to get away with it: https://mcmap.net/q/537928/-covariance-also-in-3-5-2-0 Is using a 4.0 compiler allowed?Indecorous
@Integumentary But LoginPresenter is a BaseViewPresenter, so I would assume there is some way of achieving this conversion. Am I incorrect in this?Holomorphic
@Holomorphic Yes, you are incorrect. Those casts are not allowed with generics unless the type is marked as covariant.Indecorous
@Indecorous Ugh, it's so obnoxious to go backwards in technology. Thank you :)Holomorphic
@Holomorphic This behavior would be no different in C# 5.0/.NET 4.5. Classes have never been able to be covariant in C#.Viscountess
I
59

This is a very frequently asked question. Let's rename your types:

abstract class Fruit { }                    // was BaseViewPresenter
abstract class FruitBowl<T> where T : Fruit // was BaseView
class Apple : Fruit { }                     // was LoginPresenter
class BowlOfApples : FruitBowl<Apple> {  }  // was LoginView

Your question now is:

I have a BowlOfApples, which inherits from FruitBowl<Apple>. Why can I not use it as a FruitBowl<Fruit>? An apple is a fruit, so a bowl of apples is a bowl of fruit.

No, it isn't. You can put a banana in a bowl of fruit, but you can't put a banana in a bowl of apples, and therefore a bowl of apples is not a bowl of fruit. (And by similar argument, a bowl of fruit is not a bowl of apples either.) Since the operations you can legally perform on the two types are different, they cannot be compatible.

Here is a photo of StackOverflow legend Jon Skeet demonstrating this fact:

enter image description here

The feature you want is called generic contravariance, and it is supported only on interfaces and delegate types when the compiler can prove that the variance is safe, and when the varying type is a reference type. For example, you can use an IEnumerable<Apple> in a context where IEnumerable<Fruit> is needed because the compiler can verify that there is no way that you can put a Banana into a sequence of fruit.

Do a search on "C# covariance and contravariance" on this site or on the web and you'll find many more details about how this feature works. In particular, my series of articles on how we designed and implemented this feature in C# 4 starts here: http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx

Innings answered 11/9, 2014 at 16:38 Comment(11)
Thank you, your analogy of using fruit actually makes the problem a lot clearer to me :)Holomorphic
@Rachel: You are welcome! Jon Skeet usually uses bowls, fruit, apples and bananas when answering this question; I usually use cages, animals, giraffes and tigers, which is more exciting. A paddock full of giraffes cannot be used as an animal cage because you could put a tiger into it, which would eat the giraffes. Which raises the question: would a tiger actually try to eat a giraffe? Giraffes kick pretty hard.Innings
On the other hand, Jon can actually do a demonstration with fruit (see photo) and I cannot easily do so with giraffes.Innings
@Rachel: One other thing: you have an intuitive understanding of the analogy, because the "generic type" in your mind that is "bowl of something" is very clear to you. The fact that the generic relationships in the business domain of your program are not immediately clear to you is evidence that you might not want to use generics for this purpose. I try to only use generics for cases where it is intuitively obvious what the generic type means. "baseview of some kind of presenter" is nowhere near as clear as "bowl of some kind of fruit", so maybe baseview shouldn't be generic at all.Innings
@Holomorphic or maybe there is some subset of the baseview contract that (1) is what you actually need from the object returned and (2) does not need to be generic, while some implementation detail does need to be generic. Then you can pull those members up into a base class or interface, and use that type as the return type of Resolve. The more-derived generic type can have a reference to the specific type of view it is presenting without necessarily exposing it to the consumer of the method.Integumentary
@Holomorphic for example, BaseView might have a method EatThePresenter() whose caller -- also the caller of Resolve -- does not need to know what presenter is being eaten, but the BaseView inheritor needs access to some property of the presenter to know how to eat it (i.e., does it first need to be peeled or sliced or... wait, did I say "presenter," I meant "fruit")Integumentary
@Integumentary My ultimate goal is to make the .Resolve method assign ((BaseView<BaseViewPresenter>)control).DataContext = model, and then return the UserControl object to be inserted in the UI.Holomorphic
@Holomorphic in that case you might be able to come up with something clever but it would probably be best to ask in a separate question. But unless I were looking for an exercise in generics (which I often am), I would be very likely just to cast the DataContext in the property setter or a set method -- you are already casting the object returned from Activator.CreateInstance. Casts are fairly cheap -- it's just a type check, really.Integumentary
@Integumentary Thanks, I actually did end up doing something similar and posted my own answer here :)Holomorphic
I just found this today and it is my new favorite answer on SO.Theologize
And all these answers do not address the geographic distribution of tigers and giraffes. They do not overlap ranges in nature. What to do then?Mediation
H
12

I accepted Eric's answer since it provides a great explanation of why what I wanted wasn't possible, but I also thought I'd share my solution in case anyone else runs into this same problem.

I removed the generic type parameter from my original BaseView class, and created a 2nd version of the BaseView class that included the generic type parameter and specifics for it.

The first version is used by my .Resolve() method or other code that doesn't care about the specific types, and the second version is used by any code that does care, such as the implentation of a BaseView

Here's an example of how my code ended up looking

// base classes
public abstract class BaseViewPresenter { }
public abstract class BaseView : UserControl 
{
    public BaseViewPresenter Presenter { get; set; }
}

public abstract class BaseView<T> : BaseView
    where T : BaseViewPresenter
{
    public new T Presenter
    {
        get { return base.Presenter as T; }
        set { base.Presenter = value; }
    }
}

// specific classes
public class LoginPresenter : BaseViewPresenter { }
public partial class LoginView : BaseView<LoginPresenter> 
{
     // Can now call things like Presenter.LoginPresenterMethod()
}

// updated .Resolve method used for obtaining UI object
public BaseView Resolve(BaseViewPresenter presenter)
{
    var type = model.GetType();
    var viewType = _dataTemplates[type];

    BaseView view = Activator.CreateInstance(viewType) as BaseView;
    view.Presenter = presenter;

    return view;
}
Holomorphic answered 11/9, 2014 at 18:53 Comment(4)
You could use an extension method to get the desired functionality from your original question. Just create the extension method as: public static BaseView<T> Resolve<T>(this T presenter) where T: BaseViewPresenter And then cast to BaseView<T> instead of BaseView.Oralee
@AndrewHanlon That won't work because I do not want to have to specify a specific type to use the .Resolve method. That's why my question specifies "without using specific type generics". Thank you though :)Holomorphic
If you used it in the same fashion as in your question (var login = new LoginPresenter; var ctrl = LoginPresenter.Resolve();) then it would indeed work correctly and return a BaseView<LoginPresenter> - no generic specification necessary.Oralee
@AndrewHanlon Yes I'm sorry about that code sample, I used it for simplicity. In reality my .Resolve() method is called from the code-behind a custom UserControl, and the presenter passed into it is dynamic and unknown by the UserControl at the time it evalutes. All it cares about is it is of type BaseViewPresenterHolomorphic
V
3

You're expecting to treat the type as being covariant with respect to the generic argument. Classes can never be covariant; you'd need to use an interface rather than (or in addition to) an abstract class to make it covariant with respect to T. You'd also need to be using C# 4.0.

Viscountess answered 11/9, 2014 at 16:28 Comment(3)
LoginView does inherit from BaseView<LoginPresenter>, and LoginPresenter does inherit from BaseViewPresenter, so in theory I would assume that its possible to cast LoginView to BaseView<BaseViewPresenter>. You're saying that is not possible with 3.5 though?Holomorphic
@Holomorphic And that assumption would be incorrect. The type would need to be covariant with respect to its generic argument for that to be valid. Classes in C# are never covariant. Interfaces have the potential to be covariant. The syntax to support covariance for interfaces was added in C# 4.0.Viscountess
+1. @Holomorphic - your expectation is simply unexpected. Relation between classes do not imply any relation between classes using them as generic's arguments. I.e. List<Base> and List<Derived> are siblings (have common base interface), but not derived from each other in any way. There are many posts on this topic - search for "C# covariance" (can add "Eric Lippert" for better results).Marden
M
0

My usual solution to this problem is to create an intermediary class that has access to the type-parametric class's methods through delegates. Fields can also be accessed through getters/setters.

The general pattern goes:

public abstract class Super {}

public abstract class MyAbstractType<T> where T : Super {
  public MyGeneralType AsGeneralType() {
    return MyGeneralType.Create(this);
  }

  // Depending on the context, an implicit cast operator might make things
  // look nicer, though it might be too subtle to some tastes.
  public static implicit operator MyGeneralType(MyAbstractType<T> t) {
    return MyGeneralType.Create(t);
  }

  public int field;

  public void MyMethod1() {}
  public void MyMethod2(int argument) {}
  public abstract bool MyMethod3(string argument);
}
public delegate T Getter<T>();
public delegate void Setter<T>(T value);

public delegate void MyMethod1Del();
public delegate void MyMethod2Del(int argument);
public delegate bool MyMethod3Del(string argument);

public class MyGeneralType {
  public Getter<int> FieldGetter;
  public Setter<int> FieldSetter;
  public MyMethod1Del MyMethod1;
  public MyMethod2Del MyMethod2;
  public MyMethod3Del MyMethod3;

  public static MyGeneralType Create<T>(MyAbstractType<T> t) where T : Super {
    var g = new MyGeneralType();
    g.FieldGetter = delegate { return t.field; };
    g.FieldSetter = value => { t.field = value; };
    g.MyMethod1 = t.MyMethod1;
    g.MyMethod2 = t.MyMethod2;
    g.MyMethod3 = t.MyMethod3;
    return g;
  }

  public int field {
    get { return FieldGetter(); }
    set { FieldSetter(value); }
  }
}

The above exemplifies getting all the methods and fields but normally I only need a few of them. This is a general solution to the problem and one could feasibly write a tool to generate these intermediary classes automatically, which I might at some point.

Try it here: https://dotnetfiddle.net/tLkmgR

Note that this is enough for all my cases, but you can be extra hacky with this:

public abstract class MyAbstractType<T> where T : Super {
  // ... Same everything else ...

  // data fields must become abstract getters/setters, unfortunate
  public abstract int field {
    get;
    set;
  }

  public static implicit operator MyAbstractType<Super>(MyAbstractType<T> t) {
    return MyGeneralType.Create(t);
  }
}

public class MyGeneralType : MyAbstractType<Super> {
  // ... same constructors and setter/getter
  //     fields but only keep method fields
  //     that contain the method references for
  //     implementations of abstract classes,
  //     and rename them not to clash with the
  //     actual method names ...
  public MyMethod3Del myMethod3Ref;
  
  // Implement abstract methods by calling the corresponding
  // method references.
  public override bool MyMethod3(string argument) {
    return myMethod3Ref(argument);
  }

  // Same getters/setters but with override keyword
  public override int field {
    get { return FieldGetter(); }
    set { FieldSetter(value); }
  }
}

And there you go, now you can literally cast a MyAbstractType<Sub> where Sub : Super to a MyAbstractType<Super>, although it's no longer the same object anymore, but it does retain the same methods and data, it's sort of a complex pointer.

public class Sub : Super {}
    
public class MySubType : MyAbstractType<Sub> {
  public int _field;
  public override int field {
    get { return _field; }
    set { _field = value; }
  }

  public override bool MyMethod3(string argument) {
    Console.WriteLine("hello " + argument);
    return argument == "world";
  }
}

public class MainClass {
  public static void Main() {
    MyAbstractType<Sub>   sub   = new MyAbstractType<Sub>();
    MyAbstractType<Super> super = sub;

    super.MyMethod3("hello"); // calls sub.MyMethod3();
    super.field = 10;  // sets sub.field
  }
}

This isn't as good in my opinion, the other version of MyGeneralType is a more straighforward layer over the concrete types, plus it doesn't require rewriting the data fields, but it does actually answer the question, technically. Try it here: https://dotnetfiddle.net/S3r3ke

Example

Using these abstract classes:

public abstract class Animal {
  public string name;

  public Animal(string name) {
    this.name = name;
  }

  public abstract string Sound();
}

public abstract class AnimalHouse<T> where T : Animal {
  List<T> animals;

  public AnimalHouse(T[] animals) {
    this.animals = animals.ToList();
  }

  public static implicit operator GeneralAnimalHouse(AnimalHouse<T> house) {
    return GeneralAnimalHouse.Create(house);
  }

  public List<string> HouseSounds() {
     return animals.Select(animal => animal.Sound()).ToList();
  }
}

We make this "general" variant:

public delegate List<string> HouseSoundsDel();

public class GeneralAnimalHouse {
  public HouseSoundsDel HouseSounds;

  public static GeneralAnimalHouse Create<T>(AnimalHouse<T> house) where T : Animal {
    var general = new GeneralAnimalHouse();
    general.HouseSounds = house.HouseSounds;
    return general;
  }
}

And finally with these inheritors:

public class Dog : Animal {
  public Dog(string name) : base(name) {}
  public override string Sound() {
    return name + ": woof";
  }
}

public class Cat : Animal {
  public Cat(string name) : base(name) {}
  public override string Sound() {
    return name + ": meow";
  }
}

public class DogHouse : AnimalHouse<Dog> {
  public DogHouse(params Dog[] dogs) : base(dogs) {}
}

public class CatHouse : AnimalHouse<Cat> {
  public CatHouse(params Cat[] cats) : base(cats) {}
}

We use it like this:

public class AnimalCity {
  List<GeneralAnimalHouse> houses;

  public AnimalCity(params GeneralAnimalHouse[] houses) {
    this.houses = houses.ToList();
  }

  public List<string> CitySounds() {
    var random = new Random();
    return houses.SelectMany(house => house.HouseSounds())
                 .OrderBy(x => random.Next())
                 .ToList();

  }
}
public class MainClass {
  public static void Main() {
    var fluffy   = new Cat("Fluffy");
    var miu      = new Cat("Miu");
    var snuffles = new Cat("Snuffles");

    var snoopy   = new Dog("Snoopy");
    var marley   = new Dog("Marley");
    var megan    = new Dog("Megan");

    var catHouse = new CatHouse(fluffy, miu, snuffles);
    var dogHouse = new DogHouse(snoopy, marley, megan);

    var animalCity = new AnimalCity(catHouse, dogHouse);

    foreach (var sound in animalCity.CitySounds()) {
      Console.WriteLine(sound);
    }
  }
}

Output:

Miu: meow
Snoopy: woof
Snuffles: meow
Fluffy: meow
Marley: woof
Megan: woof
Notes:
  • I added names so it's clear that the method references carry their owner's data with them, for those unfamiliar with delegates.
  • The required using statements for this code are System, System.Collections.Generic, and System.Linq.
  • You can try it here: https://dotnetfiddle.net/6qkHL3#
  • A version that makes GeneralAnimalHouse a subclass of AnimalHouse<Animal> can be found here: https://dotnetfiddle.net/XS0ljg
Manifesto answered 23/9, 2020 at 17:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.