I have a class as such
public class MyClass<T> where T : OneType
{
T MyObj { get; set; }
public MyCLass(T obj)
{
}
}
public class SubClass: MyClass<TwoType>
{
}
// snip for other similar class definition
where, TwoType
is derived from OneType
.
Now, I have this utility method
public static MyClass<T> Factory<T>(T vd)
where T : OneType
{
switch(vd.TypeName)
{
case Constant.TwoType
return new SubClass((TwoType)vd);
// snip for other type check
}
}
Which function is, obviously, checks the type of vd
, and the creates an appropriate MyClass
type. The only problem is the above code won't compile, and I don't know why
The error is
Cannot cast expression of T to TwoType
As Grzenio correctly notes, an expression of type T is not convertible to TwoType. The compiler does not know that the expression is guaranteed to be of type TwoType -- that is guaranteed by your "if" statement, but the compiler does not consider the implication of the if statement when analysing types. Rather, the compiler assumes that T can be any type satisfying the constraint, including ThreeType, a type derived from OneType but not TwoType. Obviously there is no conversion from ThreeType to TwoType, and so there is no conversion from T to TwoType either.
You can fool the compiler into allowing it by saying "well, treat the T as object, and then cast the object to TwoType". Every step along the way is legal -- T is convertible to object, and there might be a conversion from object to TwoType, so the compiler allows it.
You'll then get the same problem converting from SubClass to MyClass<T>
. Again, you can solve the problem by casting to object first.
However, this code is still wrong:
public static MyClass<T> Factory<T>(T vd)
where T:OneType
{
switch(vd.TypeName)
{
case Constant.TwoType
// WRONG
return (MyClass<T>)(object)(new SubClass((TwoType)(object)vd));
// snip for other type check
}
}
Why is it wrong? Well, consider everything that could go wrong here. For example: you say
class AnotherTwoType : TwoType { }
...
x = Factory<TwoType>(new AnotherTwoType());
What happens? We do not call the SubClass constructor because the argument is not exactly of type TwoType, it is of a type derived from TwoType. Instead of a switch, you probably want
public static MyClass<T> Factory<T>(T vd)
where T:OneType
{
if (vd is TwoType)
// STILL WRONG
return (MyClass<T>)(object)(new SubClass((TwoType)(object)vd));
// snip for other type check
}
This is still wrong. Again, think about what could go wrong:
x = Factory<OneType>(new TwoType());
Now what happens? the argument is TwoType, we make a new SubClass, and then try to convert it to MyClass<OneType>
, but there is no conversion from SubClass to MyClass<OneType>
so this will crash and die at runtime.
The correct code for your factory is
public static MyClass<T> Factory<T>(T vd)
where T:OneType
{
if (vd is TwoType && typeof(T) == typeof(TwoType))
return (MyClass<T>)(object)(new SubClass((TwoType)(object)vd));
// snip for other type check
}
Is that now all clear? You might consider a different approach entirely; when you need this many casts and runtime type checks to convince the compiler that the code is correct, that's evidence that the whole thing is a bad code smell.
Its not going to work in .Net 3.5 and below - SubClass is not of type MyClass<T>
for any T, its only of type MyClass<TwoType>
. And the generic classes do not follow the inheritance of their template type, e.g. MyClass<string>
is not a subclass of MyClass<object>
- they are completely different classes in C#.
Unfortunately I don't know any reasonable way to write your factory method.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With