2010-07-25 11 views
5

¿Considera aceptable o mala práctica crear una clase genérica abstracta que tome como parámetro de tipo una clase que se deriva de sí misma?Clases genéricas abstractas que toman parámetros de tipo derivados de esa clase

Esto permite que la clase genérica abstracta manipule instancias de la clase derivada y en particular la capacidad de crear nuevas() instancias de la clase derivada según sea necesario y puede evitar repetir código en las clases concretas que se derivan de ella.

Si es "malo", ¿qué alternativa prefiere manejar tales situaciones y cómo estructuraría el código a continuación?

Por ejemplo: -

// We pass both the wrapped class and the wrapping class as type parameters 
    // to the generic class allowing it to create instances of either as necessary. 

    public abstract class CoolClass<T, U> 
     where U : CoolClass<T, U>, new() 
    { 
     public T Value { get; private set; } 
     protected CoolClass() { } 
     public CoolClass(T value) { Value = value; } 
     public static implicit operator CoolClass<T, U>(T val) 
     { 
      // since we know the derived type and that its new(), we can 
      // new up an instance of it (which we couldn't do as an abstract class) 
      return new U() { Value = val}; 
     } 
     public static implicit operator T(CoolClass<T, U> obj) 
     { 
      return obj.Value; 
     } 
    } 

Y una segunda pregunta extra: ¿Por qué uno de estos operadores implícita trabajo y el otro no uno?

p. Ej.

public class CoolInt : CoolClass<int, CoolInt> 
    { 
     public CoolInt() { } 
     public CoolInt(int val) (val) { } 
    } 

            // Why does this not work 
     CoolInt x = 5; 
            // when this works 
     CoolInt x2 = (CoolInt)5;  
            // and this works 
     int j = x; 

Respuesta

1

Es un poco subjetivo pero no soy un gran admirador de los moldes implícitos. El código a menudo se vuelve engañoso cuando los usas y, a veces, es difícil encontrar un error si se produce por implisit cast. Si su clase está diseñada solo para usarla, no la usaría de esta manera.

¿por qué uno de estos operadores implícitos funciona y el otro no?

Porque ha definido la conversión de CoolClass<T, U>, pero no de CoolInt. Son diferentes tipos. Que funcionaría si tuviera este método en su aplicación CoolInt:

public static implicit operator CoolInt(int val) 

sobre el uso de los genéricos:

Tal uso de los genéricos crea limitaciones a su arquitectura, si lo que se necesita para crear jerarquías de herencia complejas con muchas clases (por ejemplo, introducir un nuevo nivel de abstracción podría ser complicado). Pero esto realmente depende de lo que necesitas. De hecho, utilicé esa técnica en uno de los proyectos para evitar la duplicación de código. También se podría pasar un delegado Func<U> al constructor si su CoolClass para superar nuevos() restricción :)

+0

¿Qué pasa con la pregunta sobre los parámetros de tipo genérico? (Debería haber puesto la pregunta implícita en una pregunta separada ya que es una especie de distracción para la pregunta principal.) –

+0

He actualizado la respuesta. –

+0

+1 pero tengo que estar en desacuerdo con la desaprobación de las versiones implícitas.No hay código que no pueda descubrirse sin un análisis estático o un uso riguroso del depurador; y como desarrollador espero tener que hacer esto para analizar código más complejo. El complejo no es malo. En este caso, está haciendo que el consumidor de la clase sea más capaz de hacer lo que quiera. –

1

Es un común patrón en C++, que parece un poco a esto (y bueno!):

template<typename T> class Base { 
    void foo() { T::foo(); /* Call the derived implementation*/ } 
}; 
class Derived : public Base<Derived> { 
    void foo() { /* do something*/ } 
}; 

Lo usamos para polimorfismo/herencia estática, junto con otros.

Sin embargo, en .NET donde los parámetros genéricos están en tiempo de ejecución y también hay reflexión, no estoy del todo seguro de cuál sería el beneficio. Quiero decir, tener el tipo derivado es útil, pero hay que preguntarse: ¿útil para qué y cómo difiere de la herencia directa?

+0

+1 - pero lo que realmente me gustaría poder hacer en C# es la plantilla curiosamente recursiva. ¡Es una lástima que C# no tenga mucho precompilador de lo que hablar! Es interesante que menciones ese lado de la reflexión: desarrollé un componente que compilaba dinámicamente una clase derivada o la implementación de una interfaz para construir cosas como el corte transversal. Aunque aprecio la relativa facilidad con la que se puede hacer este tipo de cosas en .Net, dijo que es una curva de aprendizaje abrupta. –

Cuestiones relacionadas