Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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

like image 331
Rachel Avatar asked Sep 11 '14 16:09

Rachel


People also ask

Can you cast to abstract class?

Q: Can you cast to an abstract class? A: Sure - of course you can. What you can't do is call an abstract method of an abstract class.

How can we convert a class into an abstract class?

Press Ctrl+Shift+R and then choose Convert Interface to Abstract Class. Right-click and choose Refactor | Convert Interface to Abstract Class from the context menu.

Can an abstract class be used as a type?

The abstract type is often used as a return type (because you don't want the caller to know what concrete type is returned: it could change later, or could vary based on the arguments of the configuration).

Is it possible to invoke a method from an abstract class?

Since you cannot instantiate an abstract class you cannot access its instance methods too. You can call only static methods of an abstract class (since an instance is not required).


2 Answers

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

like image 142
Eric Lippert Avatar answered Oct 04 '22 04:10

Eric Lippert


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; } 
like image 24
Rachel Avatar answered Oct 04 '22 03:10

Rachel