2010-01-08 8 views
8

tengo una clase como talmoldeada de Generics <T> a subclase específica

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 

donde, TwoType se deriva de OneType.

ahora que he comenzado este método de utilidad

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

cuya función es, obviamente, comprueba el tipo de vd, y la apropiada crea un tipo MyClass. El único problema es que el código anterior no compilará, y yo no sé por qué

El error es

no puede lanzar expresión de T para TwoType

+1

¿Qué error devuelve el compilador? – TJMonk15

+0

Preguntas modificadas – Graviton

Respuesta

1

increíble, lo tengo trabajo escribiendo el código como tal:

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

o

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

Pero,

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

no funciona.

Parece haber una diferencia en la fundición as y ().

0

cambiar su método de fábrica:

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

entonces no necesita el interruptor en absoluto.

+0

¿Por qué esto es downvoted? Esta es la * única * respuesta (de 7 hasta ahora) que puede compilar. – Graviton

1

No va a funcionar en .Net 3.5 y siguientes - SubClass no es del tipo MyClass<T> para cualquier T, es solo del tipo MyClass<TwoType>. Y las clases genéricas no siguen la herencia de su tipo de plantilla, p. MyClass<string> es no una subclase de MyClass<object> - son clases completamente diferentes en C#.

Desafortunadamente, no conozco ninguna forma razonable de escribir su método de fábrica.

+1

Emitir al objeto primero para engañar al compilador y permitir la conversión. –

+0

Tampoco funcionará en C# 4. Estamos agregando conversiones de variantes en interfaces genéricas y delegados, pero no en clases. –

0

No tiene el contraint para T en su método de utilidad.

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

Después de leer la respuesta anterior mía (de Grzenio) recuerdo haber tenido el mismo problema. No creo que puedas hacer lo que intentas hacer. Por lo menos, no hasta .Net 4.0 tal vez? – TJMonk15

+0

No, C# 4 no hace que esto sea mejor. Solo las interfaces y los delegados tendrán conversiones covariantes, no clases. –

+0

@Eric: suspiro ... yay por M $ – TJMonk15

0

¿Puedes voltear el diseño redondo? En lugar de crear un método de fábrica inflexible que necesita conocer todas las subclases de OneType, agregue un método abstracto al OneType que se ve así;

public MyClass<OneType> GetMyClass(); 

TwoType se convierte en el responsable de la creación de objetos SubClass, ThreeType puede volver SubTypeThree, etc.

La idea aquí es que usted está haciendo un interruptor basado en un tipo de objeto; este es siempre un buen candidato para hacer que las subclases hagan el trabajo.

EDIT 1: Ejemplo

Ej;

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

El problema es que necesito crear el 'MyClass' según' * Type 'pasado. – Graviton

+0

(publicación editada para proporcionar el ejemplo.) Espere ahora. No tienes un tipo llamado MyClass.Usted tiene un sinfín de tipos - MyClass , MyClass , etc. Pero estos son tipos totalmente diferentes. Solo puede devolver MyClass

0

esto debería funcionar:

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

de manera útil. No funciona. El mensaje es 'No se puede convertir tipo 'ClassLibrary1.SubClass' a 'ClassLibrary1.MyClass ' \t D: \ C# \ Example1_9 \ ClassLibrary1 \ ClassLibrary1 \ Class1.cs' – Graviton

+0

' SubClass' no es '' MiClase , por eso son – Graviton

+0

¿Estás seguro de que no te perdiste ese 'molde (objeto)' entre 'nueva Subclase' y' (Clase de Myclass ) '? –

18

Como Grzenio señala correctamente, una expresión de tipo T no se puede convertir en TwoType. El compilador no sabe que se garantiza que la expresión sea de tipo TwoType, eso está garantizado por su declaración "if", pero el compilador no tiene en cuenta la implicación de la sentencia if al analizar tipos.Más bien, el compilador supone que T puede ser de cualquier tipo que satisfaga la restricción, incluido ThreeType, un tipo derivado de OneType pero no TwoType. Obviamente no hay conversión de ThreeType a TwoType, por lo que tampoco hay conversión de T a TwoType.

Puede engañar al compilador para que lo permita diciendo "bueno, trate la T como objeto y luego convierta el objeto a TwoType". Cada paso en el camino es legal: T es convertible a objeto, y puede haber una conversión de objeto a TwoType, por lo que el compilador lo permite.

A continuación, obtendrá el mismo problema al convertir de SubClass a MyClass<T>. De nuevo, puedes resolver el problema lanzando al objeto primero.

Sin embargo, este código sigue siendo incorrecto:

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

Por qué está mal? Bueno, considera todo lo que podría salir mal aquí. Por ejemplo: usted dice

class AnotherTwoType : TwoType { } 
... 
x = Factory<TwoType>(new AnotherTwoType()); 

¿Qué ocurre? No llamamos al constructor SubClass porque el argumento no es exactamente del tipo TwoType, es de un tipo derivado de TwoType. En lugar de un interruptor, es probable que desee

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 
} 

Esto sigue siendo incorrecto. De nuevo, piense en lo que podría ir mal:

x = Factory<OneType>(new TwoType()); 

¿Qué pasa ahora? el argumento es TwoType, creamos una nueva SubClass y luego intentamos convertirla a MyClass<OneType>, pero no hay conversión de SubClass a MyClass<OneType>, por lo que se bloqueará y morirá en el tiempo de ejecución.

El código correcto para su fábrica es

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 
} 

es que ahora todo claro? Puede considerar un enfoque diferente por completo; cuando necesite tantos lanzamientos y verificaciones de tipo de tiempo de ejecución para convencer al compilador de que el código es correcto, eso es evidencia de que todo es un mal olor de código.

0

Esto funciona para mí:

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) 
    { 

    } 
} 

en mi forma que tenía la siguiente:

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

     MessageBox.Show(t.Working); 

EDIT: Obviamente, esto no es ideal, ya que señala encima de Eric. Si bien este código se compila y funciona técnicamente hasta cierto punto, es posible que desee encontrar una mejor solución general.

1

Sé que esta es una pregunta anterior, pero creo que también merece una respuesta a por qué esto es imposible (sin utilizar varias soluciones feos que es).

Al usar Generics, en cierto sentido, está trabajando con el código de la plantilla. El compilador usa un conjunto de reglas más estrictas para su código, ya que también debe poder compilarse en tiempo de ejecución, donde se compila la versión final.

Así que cuando crea clases o métodos con Generics, deben ser compilables para cualquier combinación posible que cumpla con las restricciones que ha establecido con sus restricciones en los parámetros genéricos.

Así que han simplificado el código aún más para mostrar lo que sucede:

primera vez que declaro 3 clases. Una clase padre y sus dos hijos:

public class Super { } 

public class Child : Super { } 

public class Sister : Super { } 

entonces declarar un método genérico donde trato de probar el tipo, y luego se convierte al tipo de niño:

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

Esto le da el mismo error que en la pregunta original, aunque intuitivamente parece un código sólido. Sin embargo, lo que realmente sucede es que el compilador verifica si el código se podrá compilar en tiempo de ejecución en cualquier versión legal del método. Eso es lo que quería decir con que se esté trabajando con plantillas, porque en tiempo de ejecución, si se llama al método con el "hermana" como parámetro de tipo, se llega a esto:

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

lo tanto, es mi suposición (I no estoy seguro, pero corríjanme si estoy equivocado), que esta restricción que tiene cuando trabaja con genéricos es porque su código no debe romperse, simplemente porque comienza a llamarlo en otro contexto, y por lo tanto no permiten usted debe escribir un código como ese en primer lugar, aunque no haya combinaciones inválidas en tiempo de compilación.

Eso es lo que me ayudó a entender por qué se pueden hacer ciertas cosas, y ciertas cosas no. Sí, puede usar "como" en lugar de encasillar, porque el compilador simplemente le da "nulo" si el molde no es válido, pero con el encasillado explícito el compilador verifica si es posible. Para los genéricos, no lo es, para garantizar que también se pueda compilar en tiempo de ejecución.