在使用类型泛型时如何正确地将类强制转换为抽象类?

Rac*_*hel 14 c# reflection .net-3.5

我有以下课程

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

public class LoginPresenter : BaseViewPresenter { }
public partial class LoginView : BaseView<LoginPresenter> {  }
Run Code Online (Sandbox Code Playgroud)

我有一个看起来像这样的方法(简化)

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>;
}
Run Code Online (Sandbox Code Playgroud)

当我使用LoginPresenter的实例调用它时

var login = new LoginPresenter();
var ctl = Resolve(login);
Run Code Online (Sandbox Code Playgroud)

该行Activator.CreateInstance(viewType)正确解析为我的新实例LoginView,但control as BaseView<BaseViewPresenter>无法正确执行转换,因此返回null.

有没有办法在不使用特定类型泛型的情况下正确地control转换成BaseView<BaseViewPresenter>

由于LoginView继承自BaseView<LoginPresenter>LoginPresenter继承BaseViewPresenter,我认为有一种方法可以转换LoginViewBaseView<BaseViewPresenter>.

我坚持使用.Net 3.5

Eri*_*ert 48

这是一个非常常见的问题.让我们重命名你的类型:

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
Run Code Online (Sandbox Code Playgroud)

你现在的问题是:

我有一个BowlOfApples,继承自FruitBowl<Apple>.为什么我不能用它FruitBowl<Fruit>?苹果是一种水果,所以一碗苹果就是一碗水果.

不,不是.你可以把香蕉放在一碗水果里,但你不能把香蕉放在一碗苹果里,因此一碗苹果不是一碗水果.(通过类似的论证,一碗水果也不是一碗苹果.)由于你可以在两种类型上合法地执行的操作是不同的,它们是不兼容的.

这是StackOverflow传奇人物Jon Skeet的照片,展示了这一事实:

在此输入图像描述

您想要的功能称为通用逆变,当编译器可以证明方差是安全的,并且变化类型是引用类型时,它仅在接口委托类型上受支持.例如,您可以IEnumerable<Apple>IEnumerable<Fruit>需要的上下文中使用a,因为编译器可以验证您无法将a Banana放入一系列水果中.

在本网站或网站上搜索"C#协方差和逆变",您将找到有关此功能如何工作的更多详细信息.特别是,关于我们如何在C#4中设计和实现此功能的系列文章从这里开始:http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-convvariance-in -C-部分one.aspx

  • 另一方面,乔恩实际上可以用水果做示范(见图)我不能轻易地用长颈鹿做. (10认同)
  • @Rachel:不客气!Jon Skeet在回答这个问题时通常会使用碗,水果,苹果和香蕉; 我通常使用笼子,动物,长颈鹿和老虎,这更令人兴奋.一个装满长颈鹿的围场不能用作动物笼子,因为你可以把虎放进去,这会吃掉长颈鹿.这提出了一个问题:老虎真的会尝试吃长颈鹿吗?长颈鹿踢得很厉害. (7认同)
  • 我今天才发现这个,这是我最喜欢的答案. (2认同)

Rac*_*hel 10

我接受了Eric的答案,因为它提供了一个很好的解释,为什么我想要的是不可能的,但我也认为我会分享我的解决方案以防其他人遇到同样的问题.

我从原始BaseView类中删除了泛型类型参数,并创建了该类的第二个版本,BaseView其中包含泛型类型参数及其详细信息.

第一个版本由我的.Resolve()方法或其他不关心特定类型的代码使用,第二个版本由任何关心的代码使用,例如实现BaseView

这是我的代码最终看起来的一个例子

// 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;
}
Run Code Online (Sandbox Code Playgroud)