我有以下课程
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
答案 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的照片,展示了这一事实:
您想要的功能称为通用逆转,只有在接口和委托类型上才支持它,当编译器可以证明方差是安全的,并且当变化类型是参考类型时。例如,您可以在需要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
语句为System
,System.Collections.Generic
和System.Linq
。GeneralAnimalHouse
成为AnimalHouse<Animal>
的子类的版本:https://dotnetfiddle.net/XS0ljg