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

时间:2014-09-11 16:24:51

标签: 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> {  }

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

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<BaseViewPresenter>无法正确执行投射,因此返回null

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

由于LoginView继承自BaseView<LoginPresenter>,而LoginPresenter继承自BaseViewPresenter,我认为有办法将LoginView转换为BaseView<BaseViewPresenter>

我坚持使用.Net 3.5

4 个答案:

答案 0 :(得分:46)

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

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<Apple>。为什么我不能将它用作FruitBowl<Fruit>?苹果是一种水果,所以一碗苹果就是一碗水果。

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

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

enter image description here

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

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

答案 1 :(得分:10)

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

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

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

以下是我的代码最终结束的示例

BaseView

答案 2 :(得分:3)

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

答案 3 :(得分:0)

我对这个问题的通常解决方案是创建一个中间类,该中间类可以通过委托访问类型参数类的方法。也可以通过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 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); }
  }
}

然后就可以了,现在您可以将MyAbstractType<Sub> where Sub : Super强制转换为MyAbstractType<Super>了,尽管它不再是同一个对象,但是它确实保留了相同的方法和数据,有点像复杂的指针。

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<Animal>的子类的版本:https://dotnetfiddle.net/XS0ljg