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

Posted

技术标签:

【中文标题】使用类型泛型时如何正确地将类转换为抽象类?【英文标题】:How to correctly cast a class to an abstract class when using type generics? 【发布时间】:2014-11-05 16:54:39 【问题描述】:

我有以下课程

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

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

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

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

当我使用 LoginPresenter 实例调用它时

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

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

有没有办法在不使用特定类型泛型的情况下将control 正确转换为BaseView&lt;BaseViewPresenter&gt;

由于LoginView 继承自BaseView&lt;LoginPresenter&gt;,而LoginPresenter 继承自BaseViewPresenter,我认为有一种方法可以将LoginView 转换为BaseView&lt;BaseViewPresenter&gt;

我坚持使用 .Net 3.5

【问题讨论】:

你能在接口中使用而不是抽象类并将T标记为协变吗?否则不允许这种类型的演员表。 @BradleyDotNET 不,我不能,我坚持使用 .Net 3.5 您无法正确地将控件转换为BaseView&lt;BaseViewPresenter&gt;,因为它是BaseView&lt;LoginPresenter&gt; 你也许可以侥幸成功:***.com/questions/9210483/covariance-also-in-3-5-2-0 是否允许使用 4.0 编译器? @phoog 但是LoginPresenterBaseViewPresenter,所以我认为有某种方法可以实现这种转换。我在这方面不正确吗? 【参考方案1】:

您希望将类型视为与泛型参数相关的协变。类永远不可能是协变的;您需要使用接口而不是(或除此之外)抽象类,以使其与T 保持协变。您还需要使用 C# 4.0。

【讨论】:

LoginView 确实继承自 BaseView&lt;LoginPresenter&gt;,而 LoginPresenter 确实继承自 BaseViewPresenter,所以理论上我认为可以将 LoginView 转换为 BaseView&lt;BaseViewPresenter&gt;。您是说 3.5 不可能做到这一点? @Rachel 这种假设是不正确的。该类型需要与其通用参数协变才能有效。 C# 中的类从不协变。接口具有协变的潜力。在 C# 4.0 中添加了支持接口协方差的语法。 +1。 @Rachel - 你的期望简直出乎意料。类之间的关系并不意味着使用它们作为泛型参数的类之间的任何关系。 IE。 List&lt;Base&gt;List&lt;Derived&gt; 是兄弟姐妹(具有共同的基本接口),但不以任何方式相互派生。关于这个主题有很多帖子 - 搜索“C# covariance”(可以添加“Eric Lippert”以获得更好的结果)。【参考方案2】:

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

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

你现在的问题是:

我有一个BowlOfApples,它继承自FruitBowl&lt;Apple&gt;。为什么我不能将其用作FruitBowl&lt;Fruit&gt;?苹果是水果,所以一碗苹果就是一碗水果。

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

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

您想要的功能称为泛型逆变,并且仅在编译器可以证明变体是安全的,并且当变体类型是引用类型时。例如,您可以在需要IEnumerable&lt;Fruit&gt; 的上下文中使用IEnumerable&lt;Apple&gt;,因为编译器可以验证您无法将Banana 放入水果序列中。

在此站点或网络上搜索“C# 协变和逆变”,您会发现有关此功能如何工作的更多详细信息。特别是,我关于我们如何在 C# 4 中设计和实现此功能的系列文章从这里开始:http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx

【讨论】:

@Rachel:不客气! Jon Skeet 在回答这个问题时通常使用碗、水果、苹果和香蕉;我一般用笼子、动物、长颈鹿和老虎,比较刺激。一个满是长颈鹿的围场不能用作动物笼子,因为你可以把一只老虎放进去,它会吃掉长颈鹿。这就提出了一个问题:老虎真的会尝试吃长颈鹿吗?长颈鹿踢得很用力。 另一方面,Jon 实际上可以用水果做示范(见照片),而我不能轻易用长颈鹿做。 @Rachel 或者可能有一些 baseview 合同的子集,(1)是您从返回的对象中实际需要的,(2)不需要是通用的,而一些实现细节 确实需要是通用的。然后,您可以将这些成员拉入基类或接口,并将该类型用作 Resolve 的返回类型。派生更多的泛型类型可以引用它所呈现的特定视图类型,而不必将其暴露给方法的使用者。 @Rachel 在这种情况下,您可能会想出一些聪明的办法,但最好单独提出一个问题。但是,除非我正在寻找泛型的练习(我经常这样做),否则我很可能只是在属性设置器或 set 方法中强制转换 DataContext——您已经在强制转换从 Activator.CreateInstance 返回的对象。演员表相当便宜——真的只是一种类型检查。 我今天才发现这个,这是我在 SO 上最喜欢的新答案。【参考方案3】:

我接受了 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;

【讨论】:

您可以使用扩展方法从原始问题中获取所需的功能。只需将扩展方法创建为: public static BaseView Resolve(this T presenter) where T: BaseViewPresenter 然后转换为 BaseView 而不是 BaseView。 @AndrewHanlon 这行不通,因为我不想指定特定类型才能使用 .Resolve 方法。这就是为什么我的问题指定“不使用特定类型泛型”的原因。不过谢谢你:) 如果您以与问题相同的方式使用它 (var login = new LoginPresenter; var ctrl = LoginPresenter.Resolve();) 那么它确实可以正常工作并返回 BaseView - 不需要通用规范。 @AndrewHanlon 是的,我很抱歉那个代码示例,我使用它是为了简单。实际上,我的 .Resolve() 方法是从自定义 UserControl 的代码隐藏中调用的,传递给它的演示者是动态的,并且在评估时 UserControl 是未知的。它只关心它的类型是BaseViewPresenter【参考方案4】:

我通常对这个问题的解决方案是创建一个中间类,它可以通过委托访问类型参数类的方法。字段也可以通过 getter/setter 访问。

一般模式是:

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

上面举例说明了获取所有方法和字段,但通常我只需要其中的几个。这是该问题的一般解决方案,并且可以编写一个工具来自动生成这些中间类,我可能会在某个时候这样做。

在这里试试:https://dotnetfiddle.net/tLkmgR

请注意,这对我所有的情况都足够了,但你可以用这个来增加技巧:

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

你去吧,现在你可以将MyAbstractType&lt;Sub&gt; where Sub : Super 转换为MyAbstractType&lt;Super&gt;,虽然它不再是同一个对象,但它确实保留了相同的方法和数据,它是一个复杂的指针。

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
  

在我看来这不太好,MyGeneralType 的另一个版本比具体类型更直接,而且它不需要重写数据字段,但它确实回答了这个问题,技术上.在这里试试:https://dotnetfiddle.net/S3r3ke

示例

使用这些抽象类:

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

我们制作了这个“通用”变体:

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;
  

最后是这些继承者:

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) 

我们这样使用它:

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

输出:

Miu: meow
Snoopy: woof
Snuffles: meow
Fluffy: meow
Marley: woof
Megan: woof
笔记: 我添加了名称,因此对于那些不熟悉委托的人来说,很明显方法引用会携带其所有者的数据。 此代码所需的using 语句是SystemSystem.Collections.GenericSystem.Linq。 你可以在这里试试:https://dotnetfiddle.net/6qkHL3# 可以在此处找到使GeneralAnimalHouse 成为AnimalHouse&lt;Animal&gt; 子类的版本:https://dotnetfiddle.net/XS0ljg

【讨论】:

以上是关于使用类型泛型时如何正确地将类转换为抽象类?的主要内容,如果未能解决你的问题,请参考以下文章

不明白java中的泛型和抽象类有啥区别,感觉他们作用一样啊,为啥要用2种方法呢

引用泛型类型的类属性

Java实训笔记——-抽象类-接口-泛型-集合

泛型类型转换 FROM 字符串

如何通过泛型类型实例化? [复制]

使用带有约束的泛型时无法隐式转换类型[重复]