tags:

views:

265

answers:

8

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"

A: 

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.

Lasse V. Karlsen
Why this is downvoted? This is the *only* answer ( out of 7 so far) that can compile.
Ngu Soon Hui
+1  A: 

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.

Grzenio
Cast to object first, to fool the compiler into allowing the conversion.
Eric Lippert
It won't work in C# 4 either. We're adding variant conversions on generic interfaces and delegates, but not on classes.
Eric Lippert
A: 

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

public static MyClass<T> Factory<T>(T vd) where T: OneType
{
    // ...
}
TJMonk15
After reading the answer above mine (from Grzenio) I remember running into the same problem. I don't think you can do what you are trying to do. Atleast, not until .Net 4.0 maybe?
TJMonk15
Nope, C# 4 doesn't make this any better. Only interfaces and delegates will have covariant conversions, not classes.
Eric Lippert
@Eric: Sigh... yay for M$
TJMonk15
http://www.penny-arcade.com/comic/2002/7/22/
Eric Lippert
A: 

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);
  }
}
Steve Cooper
The problem is I need to create the `MyClass` according to `*Type` passed in.
Ngu Soon Hui
(edited post to provide example.)Hold on now. You don't have a type called MyClass. You have an endless number of types -- MyClass<TwoType>, MyClass<ThreeType>, etc. But these are are totally different types. You can only usefully return MyClass<OneType>
Steve Cooper
A: 

This should work:

return (MyClass<T>)(object)new SubClass((TwoType)(object)vd);
Pent Ploompuu
It doesn't work. The message is `Cannot convert type 'ClassLibrary1.SubClass' to 'ClassLibrary1.MyClass<T>' D:\C#\Example1_9\ClassLibrary1\ClassLibrary1\Class1.cs`
Ngu Soon Hui
`SubClass` is not `MyClass<T>`, that's why
Ngu Soon Hui
Are you sure you didn't miss that `(object)` cast between `new SubClass` and `(Myclass<T>)`?
Pent Ploompuu
+11  A: 

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.

Eric Lippert
A: 

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.

Joshua Cauble
A: 

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

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

or return (MyClass)(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.

Ngu Soon Hui