2010-11-09 7 views
9

creo que voy a explicar mis problemas con algunos ejemplos ..Realmente no entiendo esta co/contravariancia ... ¿No puedo tener los métodos genéricos de obtención y configuración?

interface IModel {} 

class MyModel : IModel {} 

interface IRepo<T> where T: IModel { 
} 

class Repo : IRepo<MyModel> { 
} 

// Cannot implicitly convert.. An explicit convertion exists. Missing cast? 
IRepo<IModel> repo = new Repo(); 

por eso es necesario covarianza ..

interface IRepo<out T> where T: IModel { 
} 

Niza, funciona. Entonces quiero usarlo:

interface IRepo<out T> where T: IModel { 
    T ReturnSomething(); 
} 

class Repo : IRepo<MyModel> { 
    public MyModel ReturnSomething() { return default(MyModel); } 
} 

Todo bien, pero el repositorio tiene que insertar objetos también. Con un parámetro de salida, no podemos hacer esto:

// Invalid variance: The type parameter 'T' must be contravariantly valid on 'IRepo<T>.InsertSomething(T)'. 'T' is covariant. 
interface IRepo<out T> where T: IModel { 
    T ReturnSomething(); 
    void InsertSomething(T thing); 
} 

class Repo : IRepo<MyModel> { 
    public MyModel ReturnSomething() { return default(MyModel); } 
    public void InsertSomething(MyModel thing) { } 
} 

así que trato de añadir dos parámetros:

interface IRepo<out TReturn, TInsert> 
    where TReturn : IModel 
    where TInsert : IModel 
{ 
    TReturn ReturnSomething(); 
    void InsertSomething(TInsert thing); 
} 

Y me da el mismo error que en el primer ejemplo. Obtengo el mismo error al usar in TInsert

Entonces, ¿cómo podría admitir tanto la inserción como la obtención?

EDITAR: Así que he encontrado una posible solución, pero es ahora de ser óptima

interface IRepo<out TResult> where TResult : IModel { 
    TResult ReturnSomething(); 
    // I need to duplicate my constraint here.. 
    void InsertSomething<TInsert>(TInsert thing) where TInsert : IModel; 
} 


class Repo : IRepo<MyModel> { 
    public MyModel ReturnSomething() { return default(MyModel); } 
    // ... And here 
    public void InsertSomething<T>(T thing) where T: IModel { } 
} 

Edit2: En respuesta a Eric: Este es un ejemplo más completo de lo que yo Estoy tratando de lograr. Realmente me gustaría la covarianza para poder agrupar las instancias IRepo, y aún quiero que tengan métodos de agregar/actualizar usando el modelo como instancia. Entiendo que no puedo obtener seguridad de tipo de tiempo de compilación para agregar elementos, pero para este caso de uso, solo necesito leer los elementos.

interface IModel { } 
class SomeModel : IModel { } 
class OtherModel : IModel { } 

interface IRepo<T> 
{ 
    T ReturnSomething(); 
    void AddSomething(T thing); 
} 

interface ISubRepo<T> : IRepo<T> where T : IModel { } 

class SomeSubRepo : ISubRepo<SomeModel> { 
    public SomeModel ReturnSomething() { return default(SomeModel); } 
    public void AddSomething(SomeModel thing) { } 
} 

class OtherSubRepo : ISubRepo<OtherModel> { 
    public OtherModel ReturnSomething() { return default(OtherModel); } 
    public void AddSomething(OtherModel thing) { } 
} 

class Program { 
    static void Main(string[] args) 
    { 
     ISubRepo<IModel>[] everyone = new ISubRepo<IModel>[] { 
      new SomeSubRepo(), 
      new OtherSubRepo() 
     }; 

     WorkOnAll(everyone); 
    } 

    static void WorkOnAll(IEnumerable<ISubRepo<IModel>> everyone) 
    { 
     foreach(ISubRepo<IModel> repo in everyone) { 
      IModel model = repo.ReturnSomething(); 
      // Etc. 
     } 
    } 
} 
+0

Me resulta difícil entender exactamente cuál es la pregunta. ¿Estás preguntando por qué una pieza particular de código no funciona? Si es así, ¿puede proporcionar solo ese fragmento de código junto con una breve descripción de por qué cree que debería comportarse de manera diferente? –

+0

@Eric: He agregado un ejemplo más completo. Espero que esto aclare un poco. – simendsjo

Respuesta

2

Creo que la mejor opción es la de dividir la interfaz en dos:

interface IReadableRepo<out T> where T : IModel 
    { 
     T ReturnSomething(); 
    } 

    interface IWritableRepo<in T> where T : IModel 
    { 
     void InsertSomething(T thing); 
    } 

    class Repo : IReadableRepo<MyModel>, IWritableRepo<MyModel> 
    { 
     ... 
    } 

Ahora puede crear un List<IReadableRepo<IModel>> que contiene Repo instancias.

+0

¡Muy bien! Incluso puedo tener un IRepo : IReadRepo , IWriteRepo y he AlgunaBase : IRepo y SomeImpl: AlgunaBase . Muchas gracias! – simendsjo

+0

Y como nota al margen; No quiero en el repositorio de escritura, solo , ya que necesita trabajar solo en clases concretas – simendsjo

2

Si desea insertar y devolver el mismo tipo de objeto, necesita la invarianza. Solo tienes que declarar tu variable con eso en mente.

sólo tiene que utilizar el primer fragmento con esa línea de declarar la variable de recompra:

IRepo<MyModel> repo = new Repo(); 

Editar: he tomado a los 10 minutos necesarios para escribir el código necesario. Les puedo asegurar que ese trozo de código se compila en mi equipo (en Visual C# Express):

public interface IModel { 

} 

public interface IRepo<T> where T : IModel { 
    T returnModel(); 
    void putModel(T model); 
} 

public class MyModel : IModel { 

} 

public class Repo : IRepo<MyModel> { 
} 

public static class Program { 
    void main() { 
     IRepo<MyModel> repo = new Repo(); 
     var model = new MyModel(); 
     repo.putModel(model); 
     var model2 = repo.returnModel(); 
    } 
} 
+0

Tendrás que probar mis ejemplos. No compila – simendsjo

+0

Ah, lo siento. No noté que usaste MyModel en lugar de IModel. Todo el motivo de esto es el IModel.Quiero almacenar un IEnumerable > en algún lugar, y quiero crearlos mediante el uso de mi fábrica de servicio: ServiceFactory.CreateAll > ... – simendsjo

+0

Lo siento, la edición se realizó antes de que pudiera ver su respuesta. –

Cuestiones relacionadas