2011-09-11 11 views
9

Tengo un problema con devolver la recopilación y la covarianza y me preguntaba si alguien tiene una mejor solución.¿Cómo tratar la covarianza al devolver la colección en C#?

El escenario es el siguiente:

Tengo la versión 2 de la aplicación y me gustaría mantener la aplicación versión completamente separado (a pesar de que podrían tener la misma lógica). En la implementación, me gustaría devolver una lista de elementos y, por lo tanto, en la interfaz, devolvería una lista de la interfaz del artículo. Sin embargo, en la implementación real de la interfaz, me gustaría devolver el objeto concreto del artículo. En el código, se ve algo como esto.

interface IItem 
{ 
    // some properties here 
} 

interface IResult 
{ 
    IList<IItem> Items { get; } 
} 

Entonces, habría 2 espacios de nombres que tienen una implementación concreta de estas interfaces. Por ejemplo,

Espacio de nombres Version1

class Item : IItem 

class Result : IResult 
{ 
    public List<Item> Items 
    { 
     get { // get the list from somewhere } 
    } 

    IList<IItem> IResult.Items 
    { 
     get 
     { 
      // due to covariance, i have to convert it 
      return this.Items.ToList<IItem>(); 
     } 
    } 
} 

Habrá otra aplicación del mismo espacio de nombres bajo versión 2.

Para crear estos objetos, habrá una fábrica que tomará la versión y creará el tipo de concreto apropiado según sea necesario.

Si la persona que llama conoce la versión exacta y hace lo siguiente, el código funciona bien

Version1.Result result = new Version1.Result(); 
result.Items.Add(//something); 

Sin embargo, me gustaría que el usuario sea capaz de hacer algo como esto.

IResult result = // create from factory 
result.Items.Add(//something); 

Pero, debido a que se convierte en otra lista, el complemento no va a hacer nada porque el artículo no se volverá a añadir al objeto resultado original.

puedo pensar en algunas soluciones tales como:

  1. pude sincronizar las dos listas, pero que parece ser un trabajo extra para hacer
  2. retorno IEnumerable en lugar de IList y añadir un método para crear/Eliminación de colecciones
  3. Crear una colección personalizada que lleva el TConcrete y tInterfaz

entiendo por qué ocurre esto (debido al tipo de seguridad y todo), pero ninguno de workaroun d Creo que puede parece muy elegante. ¿Alguien tiene mejores soluciones o sugerencias?

¡Gracias de antemano!

actualización

Después de pensar en esto más, creo que puedo hacer lo siguiente:

public interface ICustomCollection<TInterface> : ICollection<TInterface> 
{ 
} 

public class CustomCollection<TConcrete, TInterface> : ICustomCollection<TInterface> where TConcrete : class, TInterface 
{ 
    public void Add(TConcrete item) 
    { 
     // do add 
    } 

    void ICustomCollection<TInterface>.Add(TInterface item) 
    { 
     // validate that item is TConcrete and add to the collection. 
     // otherwise throw exception indicating that the add is not allowed due to incompatible type 
    } 

    // rest of the implementation 
} 

entonces puedo tengo

interface IResult 
{ 
    ICustomCollection<IItem> Items { get; } 
} 

then for implementation, I will have 

class Result : IResult 
{ 
    public CustomCollection<Item, IItem> Items { get; } 

    ICustomCollection<TItem> IResult.Items 
    { 
     get { return this.Items; } 
    } 
} 

de esa manera, si la persona que llama es accediendo a la clase Result, irá a través de CustomCollection.Add (elemento TConcrete) que ya es TConcrete.Si la persona que llama está accediendo a través de la interfaz IResult, irá a customCollection.Add (elemento TInterface) y la validación se realizará y se asegurará de que el tipo sea realmente TConcrete.

Lo intentaré y veré si esto funciona.

+1

Creo que ir con la opción n. ° 2 requerirá la menor cantidad de código. También reducirá el área de superficie de su interfaz, ya que la implementación no será responsable del soporte completo de IList, que las personas que llaman probablemente no necesiten. –

+0

Su solución se ve bien, pero ¿por qué usa 'ICustomCollection ' y no 'ICollection ' directamente? – svick

+0

Totalmente podría.La única razón es que tengo algunos métodos específicamente para la recopilación que no está disponible para el ICollection normal al que se debe acceder mediante código interno, que olvidé mencionar. – Khronos

Respuesta

2

El problema al que se enfrenta es porque quiere exponer un tipo que dice (entre otras cosas) "puede agregar cualquier IItem", pero lo que realmente hará es "Puede agregar solo Item s" . Creo que la mejor solución sería exponer realmente IList<Item>. El código que usa esto tendrá que saber sobre el concreto Item de todos modos, si debe agregarlos a la lista.

Pero si realmente quieres hacer esto, creo que la solución más limpia sería 3., si te entiendo correctamente. Va a ser un contenedor alrededor de IList<TConcrete> e implementará IList<TInterface>. Si intentas incluir algo que no sea TConcrete, arrojará una excepción.

0

+1 al comentario de Brent: devuelva la colección no modificable y proporcione los métodos de modificación en las clases.

Sólo para reiterar por qué no se puede conseguir 1 trabaja en forma explicable:

Si intenta agregar a List<Item> elemento que simplemente implementa IItem pero no es del tipo (o se deriva de) Artículo no será capaz de almacenar este nuevo elemento en la lista. Como resultado, el comportamiento de la interfaz será muy inconsistente: algunos elementos que implementan IItem pueden agregarse bien, el sime podría fallar, y cuando cambie la implementación a la versión 2, el comportamiento también cambiará.

La solución simple sería almacenar IList<IItem> insitead de List<Item>, pero exponer la colección directamente requiere una reflexión cuidadosa.

0

El problema es que Microsoft usa una interfaz, IList <T>, para describir tanto las colecciones a las que se pueden agregar como las colecciones que no. Aunque sería teóricamente posible para una clase implementar IList <Cat> de forma mutable y también implementar IList <Animal> de manera inmutable (la propiedad ReadOnly de la interfaz anterior devolvería false, y la última devolvería true), no hay forma de que una clase para especificar que implementa IList <Cat> de una forma, e IList <T> donde cat: T, de otra manera. Deseo Microsoft había hecho IList <T> Lista <T> implemento IReadableByIndex < cabo T >, IWritableByIndex < en T >, IReadWriteByIndex <T>, IAppendable < en T > y ICountable, ya que los habría permitido para la covarianza y contravarianza, pero no lo hicieron. Puede ser útil implementar dichas interfaces por ti mismo y definir wrappers para ellas, dependiendo de la medida en que la covarianza sea útil.

Cuestiones relacionadas