开发者

Cast from Generics<T> to Specific SubClass

开发者 https://www.devze.com 2022-12-16 06:56 出处:网络
I have a class as such public class MyClass<T> where T : OneType { T MyObj { get; set; } public MyCLass(T obj)

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.


I know this is an old question, but I think it also deserves an answer to why this is impossible (without using various ugly workarounds that is).

When using Generics, you are, in a sense, working with template code. The compiler uses a stricter set of rules for your code, because it has to be compileable at runtime as well, where the final version is compiled.

So when you create classes or methods with Generics, they must be compileable for any possible combinations that abides to the restrictions you have set with your constraints on the generic parameters.

So I have simplified your code even further to show you what happens:

I first declare 3 classes. A parent class and two children:

public class Super { }

public class Child : Super { }

public class Sister : Super { }

I then declare a generic method where I try to test the type, and then cast to the child type:

public void InvalidMethod<T>(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...
}

This gives you the same error as in the original question, though intuitively it looks like solid code. However, what really happens, is that the compiler checks, if the code will be compileable at runtime in any legal version of the method. That is what I meant by that your are working with templates, because at runtime, if you call the method with "Sister" as your type parameter, you would get this:

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...
}

It is therefore my guess (I do not know for sure, but please correct me if I am wrong), that this restriction you have when working with generics, is because your code should not break, simply because you start calling it in another context, and therefore they do not allow you to write code like that in the first place, even though there might not be any invalid combinations at compile time.

That is what helped me understand why certain things can be done, and certain things not. Yes, you can use "as" instead of typecasting, because the compiler just gives you "null" if the cast is invalid, but with explicit typecasting the compiler checks if it is possible. For generics, it is not, to ensure it can compile at runtime as well.


Amazing, I got it working by writing the code as such:

return (new SubClass(vd as TwoType) as MyClass<T>);

or

return (MyClass<T>)(object)new SubClass((TwoType)(object)vd );

But,

return (MyClass<T>)new SubClass((TwoType)vd );

doesn't work.

There seems to be a difference in as and () casting.


Change your factory method:

public static MyClass<T> Factory<T>(T vd)
    where T: OneType
{
    return new MyClass<T>(vd);
}

then you don't need the switch at all.


You don't have the contraint for T on your utility method.

public static MyClass<T> Factory<T>(T vd) where T: OneType
{
    // ...
}


This should work:

return (MyClass<T>)(object)new SubClass((TwoType)(object)vd);


Can you flip the design round? Instead of creating an inflexible factory method which needs to know about every subclass of OneType, add an abstract method to OneType that looks like this;

public MyClass<OneType> GetMyClass();

TwoType becomes responsible for creating SubClass objects, ThreeType can return SubTypeThree, etc.

The clue here is that you're doing a switch based on an object's type; this is always a good candidate for getting the subclasses to do the work.

EDIT 1: Example

Eg;

public class TwoType: MyClass<TwoType>
{
  public override MyClass<OneType> GetMyClass()
  {
      return new SubClass(this);
  }
}


This works for me:

public class OneType
{

}

public class MyClass<T> where T : OneType
{
    T MyObj
    { get; set; }
    public MyClass(T obj)
    {
    }
    public static MyClass<T> Factory<T>(T vd)
      where T : OneType
    {
        if (vd is TwoType)
        {
            return (MyClass<T>)(object)new SubClass(vd as TwoType);
        }
        return null;
    }

    public string Working
    {
        get { return this.GetType().Name; }
    }

}

public class TwoType : OneType
{

}


public class SubClass : MyClass<TwoType>
{
    public SubClass(TwoType obj)
        : base(obj)
    {

    }
}

in my form I had this:

        MyClass<TwoType> t = MyClass<TwoType>.Factory<TwoType>(new TwoType());

        MessageBox.Show(t.Working);

EDIT: Obviously this is not ideal as Eric points out above. While this code technically compiles and works to a degree you might want to find a better overall solution.

0

精彩评论

暂无评论...
验证码 换一张
取 消