从Generics 转换为特定子类
我有一个class级
public class MyClass where T : OneType { T MyObj { get; set; } public MyCLass(T obj) { } } public class SubClass: MyClass { } // snip for other similar class definition
其中, TwoType
派生自OneType
。
现在,我有这个实用方法
public static MyClass Factory(T vd) where T : OneType { switch(vd.TypeName) { case Constant.TwoType return new SubClass((TwoType)vd); // snip for other type check } }
显然,哪个函数检查vd
的类型,并创建适当的MyClass
类型。 唯一的问题是上面的代码不会编译,我不知道为什么
错误是
无法将T的表达式转换为TwoType
正如Grzenio正确指出的那样,T类型的表达式不能转换为TwoType。 编译器不知道表达式是保证类型为TwoType – 由“if”语句保证,但编译器在分析类型时不考虑if语句的含义。 相反,编译器假定T可以是满足约束的任何类型,包括ThreeType,一种从OneType派生但不是TwoType的类型。 显然没有从ThreeType到TwoType的转换,因此也没有从T到TwoType的转换。
您可以通过说“好吧,将T视为对象,然后将对象转换为TwoType”来欺骗编译器允许它。 沿途的每一步都是合法的 – T可以转换为对象,并且可能存在从对象到TwoType的转换,因此编译器允许它。
然后,您将遇到从SubClass转换为MyClass
的相同问题。 同样,您可以先通过强制转换为对象来解决问题。
但是,这段代码仍然是错误的:
public static MyClass Factory (T vd) where T:OneType { switch(vd.TypeName) { case Constant.TwoType // WRONG return (MyClass )(object)(new SubClass((TwoType)(object)vd)); // snip for other type check } }
为什么这是错的? 好吧,考虑一下这里可能出错的一切。 例如:你说
class AnotherTwoType : TwoType { } ... x = Factory(new AnotherTwoType());
怎么了? 我们不调用SubClass构造函数,因为参数不完全是TwoType类型,它是从TwoType派生的类型。 你可能想要而不是开关
public static MyClass Factory (T vd) where T:OneType { if (vd is TwoType) // STILL WRONG return (MyClass )(object)(new SubClass((TwoType)(object)vd)); // snip for other type check }
这仍然是错误的。 再次,想想可能出现的问题:
x = Factory(new TwoType());
现在发生了什么? 参数是TwoType,我们创建一个新的SubClass,然后尝试将其转换为MyClass
,但是没有从SubClass到MyClass
转换,所以这会在运行时崩溃并死掉。
您工厂的正确代码是
public static MyClass Factory (T vd) where T:OneType { if (vd is TwoType && typeof(T) == typeof(TwoType)) return (MyClass )(object)(new SubClass((TwoType)(object)vd)); // snip for other type check }
现在这一切都清楚了吗? 你可以完全考虑一种不同的方法; 当你需要这么多的强制转换和运行时类型检查来说服编译器代码是正确的时,certificate整个事情都是一个糟糕的代码味道。
它无法在.Net 3.5及更低版本中运行 – 对于任何T,SubClass都不是MyClass
类型,它只是MyClass
类型。 并且generics类不遵循其模板类型的inheritance,例如, MyClass
不是 MyClass
的子类 – 它们在C#中是完全不同的类。
不幸的是,我不知道编写工厂方法的任何合理方法。
我知道这是一个老问题,但我认为它也值得回答为什么这是不可能的(没有使用各种丑陋的解决方法)。
使用generics时,从某种意义上说,您使用的是模板代码。 编译器为您的代码使用了更严格的规则集,因为它必须在运行时可编译,并在其中编译最终版本。
因此,当您使用generics创建类或方法时,它们必须可编译为任何可能的组合 , 这些组合遵循您对通用参数的约束设置的限制。
所以我进一步简化了你的代码,向你展示了会发生什么:
我首先申报3个class级。 父母class和两个孩子:
public class Super { } public class Child : Super { } public class Sister : Super { }
然后我声明一个generics方法,我尝试测试类型,然后转换为子类型:
public void InvalidMethod(T input) where T : Super { Child castedReference = null; if (input is Child) { // This intuitively ought to be valid C#, but generates a compiletime error castedReference = (Child)input; } // Do stuff... }
这会给你带来与原始问题相同的错误,但直观地说它看起来像是固态代码。 然而,实际发生的是,编译器检查代码是否可以在运行时在任何合法版本的方法中编译。 这就是我的意思,你正在使用模板,因为在运行时,如果你用“Sister”作为你的类型参数调用方法,你会得到这个:
public void InvalidMethod(Sister input) { Child castedReference = null; // Following 'if' is never true if (input is Child) { // Following statement is invalid C# castedReference = (Child)input; } // Do stuff... }
因此我猜测(我不确定,但如果我错了请纠正我),你在使用generics时遇到的限制是因为你的代码不应该破坏,只是因为你开始在另一个上下文中调用它因此,即使在编译时可能没有任何无效组合,它们也不允许您首先编写类似的代码。
这就是帮助我理解为什么某些事情可以完成,而某些事情不能完成的原因。 是的,您可以使用“as”而不是类型转换,因为如果强制转换,编译器只会为您提供“null”,但是通过显式类型转换,编译器会检查是否可行。 对于generics,它不是,以确保它也可以在运行时编译。
太棒了,我通过编写代码来实现它:
return (new SubClass(vd as TwoType) as MyClass);
要么
return (MyClass)(object)new SubClass((TwoType)(object)vd );
但,
return (MyClass)new SubClass((TwoType)vd );
不起作用。
as
和()
铸造似乎有所不同。
更改您的工厂方法:
public static MyClass Factory (T vd) where T: OneType { return new MyClass (vd); }
然后你根本不需要开关。
您的实用程序方法没有T的约束。
public static MyClass Factory (T vd) where T: OneType { // ... }
这应该工作:
return (MyClass)(object)new SubClass((TwoType)(object)vd);
你可以翻转设计吗? 而不是创建一个需要了解OneType
每个子类的不灵活的工厂方法,而是向OneType
添加一个类似于此的抽象方法;
public MyClass GetMyClass();
TwoType负责创建SubClass
对象, ThreeType
可以返回SubTypeThree
等。
这里的线索是你正在根据对象的类型进行切换; 这对于让子类完成工作总是一个很好的选择。
编辑1:示例
例如;
public class TwoType: MyClass { public override MyClass GetMyClass() { return new SubClass(this); } }
这对我有用:
public class OneType { } public class MyClass where T : OneType { T MyObj { get; set; } public MyClass(T obj) { } public static MyClass Factory (T vd) where T : OneType { if (vd is TwoType) { return (MyClass )(object)new SubClass(vd as TwoType); } return null; } public string Working { get { return this.GetType().Name; } } } public class TwoType : OneType { } public class SubClass : MyClass { public SubClass(TwoType obj) : base(obj) { } }
在我的forms我有这个:
MyClass t = MyClass .Factory (new TwoType()); MessageBox.Show(t.Working);
编辑:显然这不是理想,正如埃里克指出的那样。 虽然此代码在技术上编译并在某种程度上工作,但您可能希望找到更好的整体解决方案。