2010-02-11 11 views
12

Tal vez estoy haciendo todo mal.C# Generics - ¿Cómo devuelvo un tipo específico?

Tengo un montón de clases que se derivan de la clase "Modelo", una clase base con un conjunto de propiedades y métodos comunes. Quiero a todos a poner en práctica un conjunto de funcionalidades:

public abstract void Create(); 
public abstract T Read<T>(Guid ID); //<--Focus on this one 
public abstract void Update(); 
public abstract void Delete(); 

Entonces implementarla en una clase de niño como "cita" de esta manera:

public override T Read<T>(Guid ID) 
{ 
    var appt = db.Appointments.First(a => a.AppointmentID.Equals(ID)); 
    var appointment = new Appointment() 
    { 
    DateEnd = appt.dateEnd.GetValueOrDefault(), 
    Location = appt.location, 
    Summary = appt.summary 
    }; 
return appointment; 
} 

Esto arroja una excepción "No se puede convertir implícitamente escriba 'Cita' a T ". Si cambio la firma del método a "anulación pública Appointment Read (Guid ID)", el compilador dice que no he implementado el método abstracto en la clase secundaria.

¿Qué me estoy perdiendo? ¿Alguien puede darme algunas muestras de código?

+5

Cuál es la motivación detrás de tener su método Read genérico? –

+0

+1 Me gusta la pregunta ya que estaba tratando de hacer algo similar. Luego me di cuenta de que lo estaba haciendo mal, terminé con una solución muy similar a la de Greg. Si esta pregunta hubiera estado aquí hace 4 días, no habría pasado tanto tiempo tratando de descubrir qué estaba haciendo mal. – IAbstract

+3

¿De qué sirve usar genéricos si no está utilizando 'T' en su método y quiere devolver' Cita '? – jason

Respuesta

19

¡Parece que podría usar una clase base genérica! Considere algo como lo siguiente:

class Model<T> 
{ 
    public abstract T Read(Guid ID); 
} 

class Appointment : Model<Appointment> 
{ 
    public override Appointment Read(Guid ID) { } 
} 

Ahora todas sus subclases están fuertemente tipadas. Por supuesto, la compensación es que ya no tiene una sola clase base. Un Model<Appointment> no es lo mismo que un Model<Customer>. En general, no he encontrado esto como un problema, porque hay poca funcionalidad común: las interfaces son similares, pero todas funcionan con diferentes tipos.

Si desea una base común, ciertamente puede engañar e implementar una interfaz basada en object que hace las mismas tareas generales. Por ejemplo, algo en el espíritu de (no probado, pero la idea está ahí):

interface IModelEntity 
{ 
    object Read(Guid ID); 
} 

class Model<T> : IModelEntity 
{ 
    public T Read(Guid ID) 
    { 
     return this.OnRead(ID); // Call the abstract read implementation 
    } 

    object IModelEntity.Read(Guid ID) 
    { 
     return this.OnRead(ID); // Call the abstract read implementation 
    } 

    protected abstract virtual T OnRead(Guid ID); 
} 

class Appointment : Model<Appointment> 
{ 
    protected override Appointment OnRead(Guid ID) { /* Do Read Stuff */ } 
} 
+0

+1 - Buen trabajo en esto.Es bueno saber que alguien se tomará el tiempo para examinar el problema raíz cuando los demás (yo) son demasiado vagos. – ChaosPandion

+0

De acuerdo. Aprecio que vayas más allá. – Rap

4

Necesita encajonar y lanzar. Me pregunto por qué este método es genérico.

return (T)(object)appointment; 
+2

Acepto, * ¿por qué este método es genérico * ?. No veo el punto en 'T' si el método está usando explícitamente' Citas 'y devuelve un tipo 'Cita'. En lugar de hacer que el método sea genérico, la clase debe ser genérica como lo recomienda Greg D, IMO. – IAbstract

+0

+1 para la respuesta específica a por qué los problemas de devolución. – IAbstract

1

Hay algo acerca de este diseño moderno.

Independientemente de si es o no es la clase de plantilla Model, poniendo un parámetro de plantilla en el método Read no tiene mucho sentido como un método de instancia.

Normalmente tendrías algo así como lo que Greg D publicó.

+0

+1 para llamar a mi diseño funky. :-) – Rap

5

¿Funcionaría?

public abstract T Read<T>(Guid ID) where T : IAppointment; 
+0

+1 - Desea utilizar restricciones genéricas como se muestra en este fragmento. – Finglas

+2

No, eso no funciona. Estás razonando sobre la restricción en la dirección incorrecta; la restricción de parámetro de tipo restringe el argumento * type *. Supongamos que hay dos clases, Appointment y Frob, que implementan IAppointment. Leer sería legal porque Frob implementa IAppointment. ¡Pero no puede convertir un objeto de tipo Appointment to Frob solo porque ambos implementan IAppointment! –

1

Si public abstract T Read<T>(Guid ID); de Model será única vez volvemos tipos derivados de Model, considerar el cambio de la firma de

public abstract class Model 
{ 
    public abstract void Create(); 
    public abstract Model Read(Guid ID); //<--here 
    public abstract void Update(); 
    public abstract void Delete(); 
} 
1

en su clase de nombramiento agregar este

public class Appointment<T> : Model where T : Appointment 
+0

Lo había intentado antes. "Las restricciones para sobrescribir y los métodos de implementación explícitos de la interfaz se heredan del método base, por lo que no se pueden especificar directamente". Gracias, sin embargo. – Rap

0

también puede llamar a: var x = myModel.Read<Appointment>();

3

Primero debe enviar a object al T. El motivo radica en que object se encuentra en la parte superior de la cadena de herencia.No hay una correlación directa de Appointment a T; por lo tanto, debe retroceder al object, luego encuentre el camino de regreso al T.

que proporcionan esta respuesta para dar una explicación de por qué la instrucción de retorno no funcionaría a menos que fue echado por partida doble - y en apoyo de las respuestas dadas por el Caos & Greg

+0

+1 para proporcionar claridad. Eso ayudó. – Rap

+0

de nada! – IAbstract

2

Primera, sugeriría conviertes tu clase base en una interfaz. Si esa es una opción para usted, esto también se reducirá en un código ligeramente menos recargado, ya que puede deshacerse de las palabras clave abstract y public en la declaración de interfaz y omitir el override en las clases de implementación.

Segunda, ya que su implementación de Appointment.Read sugiere, podría cambiar el método de firma de Read para devolver un objeto modelo.

Ambos sugirieron cambios darían como resultado lo siguiente:

public interface IModel 
{ 
    void Create(); 
    IModel Read(Guid ID); 
    void Update(); 
    void Delete(); 
} 

Tercer, me parece que Read realmente debería ser un método de fábrica. En su código actual, primero necesita crear una instancia de un objeto Appointment antes de poder llamar al método Read para recuperar otro objeto Appointment. Esto me parece incorrecto, desde una perspectiva de diseño de clase.

¿Qué tal si toma Read fuera de la clase base/interfaz y lo proporciona como método estático en todas las clases derivadas/implementadas? Por ejemplo:

public class Appointment : IModel 
{ 
    public static Appointment Read(Guid ID) 
    { 
     return new Appointment() 
       { 
        ... 
       }; 
    } 
} 

También podría considerar mover Read en una estática (de fábrica) de clase; sin embargo, tendría que ser lo suficientemente inteligente como para saber qué tipo de objeto debería devolver. Esto funcionaría, p. si tuviera una tabla en su base de datos que mapearía un GUID para el tipo de objeto correspondiente.

Editar: La última sugerencia anterior solía ser la siguiente:

En tercer lugar, si esto es correcto hasta el momento, la siguiente pregunta sería si es o no Read debe ser un método estático en su lugar. Si es así, podría hacerse static y moverse a una clase estática Model. El método actuaría entonces como un método de fábrica que construye IModel objetos de una base de datos:

Guid guid = ...; 
IModel someModel = Model.Read(guid); 
Cuestiones relacionadas