c#

c# - "Interfaz no implementada" al devolver el tipo derivado



(12)

¿Por qué no simplemente devuelve una lista desde su interfaz ...

public interface ISomeData { List<string> Data { get; } }

Si sabe que sus consumidores van a iterar sobre él (IEnumerable) y agregarlo (IList), entonces parece lógico simplemente devolver una Lista.

El siguiente código:

public interface ISomeData { IEnumerable<string> Data { get; } } public class MyData : ISomeData { private List<string> m_MyData = new List<string>(); public List<string> Data { get { return m_MyData; } } }

Produce el siguiente error:

error CS0738: ''InheritanceTest.MyData'' no implementa el miembro de la interfaz ''InheritanceTest.ISomeData.Data''. ''InheritanceTest.MyData.Data'' no puede implementar ''InheritanceTest.ISomeData.Data'' porque no tiene el tipo de retorno coincidente de ''System.Collections.Generic.IEnumerable''.

Como una Lista <T> implementa IEnumerable <T>, uno pensaría que mi clase implementaría la interfaz. ¿Puede alguien explicar cuál es la razón fundamental para que esto no se compile?

Como puedo ver, hay dos soluciones posibles:

  1. Cambie la interfaz para ser más específico y requiera que se implemente IList.
  2. Cambie mi clase (MyData) para devolver IEnumerable e implemente la interfaz original.

Ahora supongamos que también tengo el siguiente código:

public class ConsumerA { static void IterateOverCollection(ISomeData data) { foreach (string prop in data.MyData) { /*do stuff*/ } } } public class ConsumerB { static void RandomAccess(MyData data) { data.Data[1] = "this line is invalid if MyPropList return an IEnumerable<string>"; } }

Podría cambiar mi interfaz para requerir que se implemente IList (opción 1), pero eso limita quién puede implementar la interfaz y el número de clases que se pueden pasar a ConsumerA. O bien, podría cambiar la implementación (clase MyData) para que devuelva un IEnumerable en lugar de una lista (opción 2), pero luego ConsumerB tendría que ser reescrito.

Esto parece ser un defecto de C # a menos que alguien me pueda iluminar.


¿Qué pasa si cambió su interfaz para extender IEnumerable para que pueda enumerar el objeto y editar los datos a través de la propiedad de la clase.

public interface ISomeData : IEnumerable<string> { IEnumerable<string> Data { get; } } public class MyData : ISomeData { private List<string> m_MyData = new List<string>(); public List<string> Data { get { return m_MyData; } public IEnumerator<string> GetEnumerator() { return Data; } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } }


¿Qué sucede si accede a su objeto MyData a través de la interfaz ISomeData? En ese caso, IEnumerable podría ser de un tipo subyacente no asignable a una lista.

IEnumerable<string> iss = null; List<string> ss = iss; //compiler error

EDITAR:

Entiendo lo que quieres decir con tus comentarios.

De todos modos, lo que haría en tu caso sería:

public interface ISomeData<T> where T: IEnumerable<string> { T Data { get; } } public class MyData : ISomeData<List<string>> { private List<string> m_MyData = new List<string>(); public List<string> Data { get { return m_MyData; } } }

Convirtiendo a la interfaz genérica con las ofertas de restricciones adecuadas, creo que es lo mejor de la flexibilidad y la legibilidad.


Hmm, es una deficiencia, yo diría que no.

De cualquier manera, me gustaría solucionarlo como la respuesta de Darin o, si explícitamente también quieres un acceso a la Lista, podrías hacerlo así:

public class MyData : ISomeData { IEnumerable<string> ISomeData.Data { get { return _myData; } } public List<string> Data { get { return (List<string>)((ISomeData)this).Data; } } }


La firma del miembro no puede ser diferente.

Aún puede devolver la List<string> dentro del método get , pero la firma debe ser la misma que la interfaz.

Así que simplemente cambia:

public List<string> Data { get { return m_MyData; } }

a

public IEnumerable<string> Data { get { return m_MyData; } }

En cuanto a su otra opción: cambiar la interfaz para devolver una List . Esto debe ser evitado. Es una encapsulation deficiente y se considera un olor codificado .


Lamentablemente, el tipo de devolución debe coincidir. Lo que está buscando se llama ''covarianza de tipo de retorno'' y C # no lo admite.

http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=90909

Eric Lippert, desarrollador sénior del equipo compilador de C #, menciona en su blog que no planean respaldar la covarianza del tipo de retorno.

"Ese tipo de varianza se llama" covarianza de tipo de retorno ". Como mencioné al principio de esta serie, (a) esta serie no trata de ese tipo de varianza, y (b) no tenemos planes de implementar ese tipo de varianza en C #. "

http://blogs.msdn.com/ericlippert/archive/2008/05/07/covariance-and-contravariance-part-twelve-to-infinity-but-not-beyond.aspx

Vale la pena leer los artículos de Eric sobre covarianza y contravarianza.

http://blogs.msdn.com/ericlippert/archive/tags/Covariance+and+Contravariance/default.aspx


Las interfaces requieren que la firma del método coincida exactamente con la firma del contrato.

Aquí hay un ejemplo más simple que tampoco compilará:

interface IFoo { Object GetFoo(); } class Foo : IFoo { public String GetFoo() { return ""; } }

Ahora, en cuanto a qué hacer al respecto, dejaría que la interfaz dicte la implementación. Si desea que el contrato sea IEnumerable<T> entonces eso es lo que debería ser en la clase también. La interfaz es lo más importante aquí ya que la implementación es libre de ser tan flexible como debe ser.

Solo asegúrese de que IEnumerable<T> sea ​​la mejor opción aquí. (Todo esto es altamente subjetivo ya que no sé mucho sobre su dominio o el propósito de estos tipos en su aplicación. ¡Buena suerte!)


Me encontré con situaciones similares y quiero dar un ejemplo más concreto de por qué esto no está permitido. Las partes genéricas de mi aplicación se ocupan de una interfaz que contiene propiedades y proporciona datos a través de esa interfaz a otra parte de la aplicación que contiene clases concretas que implementan estas interfaces.

El objetivo que creo es similar al tuyo: las clases de conrete tienen más funcionalidad, y la interfaz proporciona el mínimo absoluto necesario para la parte genérica de la aplicación. Es una buena práctica para la Interfaz exponer la menor cantidad de funcionalidad necesaria, ya que maximiza la compatibilidad / reutilización. Es decir, no requiere que un implementador de la interfaz implemente más de lo necesario.

Sin embargo, considere si tuvo un colocador en esa propiedad. Usted crea una instancia de su clase concreta y la pasa a un ayudante genérico que toma un ISomeData. En el código de ese ayudante, están trabajando con un ISomeData. Sin ningún tipo de T genérico en el que T: new () o patrón de fábrica, no pueden crear nuevas instancias para acceder a los datos que coinciden con su implementación concreta. Simplemente devuelven una lista que implementa IEnumerable:

instanceISomeData.Data = new SomeOtherTypeImplementingIEnumerable ();

Si SomeOtherTypeImplementingIEnumerable no hereda List, pero ha implementado .Data como una lista, esta es una asignación no válida. Si el compilador le permitió hacer esto, escenarios como este se bloquearían en tiempo de ejecución porque SomeOtherTypeImplementingIEnumerable no se puede convertir a List. Sin embargo, en el contexto de este ayudante que trabaja con ISomeData, no ha violado la interfaz ISomeData de todos modos, y la asignación de cualquier tipo que admita IEnumerable a .Data debe ser válida. Entonces su implementación, si el compilador lo permitió, podría romper el código perfectamente bueno trabajando con la Interfaz.

Es por eso que no puede implementar .Data como un tipo derivado. Estás restringiendo más estrictamente la implementación de .Data para que solo se use la lista, en lugar de cualquier IEnumerable. Por lo tanto, aunque la interfaz dice "cualquier IEnumerable está permitido", su implementación concreta provocaría que el código preexistente que soporta ISomeData se rompa repentinamente al trabajar con su implementación de la interfaz.

Por supuesto que realmente no te encuentras con este escenario con solo un getter, pero complicaría las cosas para permitirlo en los escenarios de obtención, pero no de otra manera.

Normalmente uso la solución de Jake o la de Alioto, dependiendo de cuán quisquilloso me sienta en este momento.


Podrías implementar de esta manera:

public class MyData : ISomeData { private List<string> m_MyData = new List<string>(); public IEnumerable<string> Data { get { return m_MyData; } } }


Por lo que desea hacer, es probable que desee implementar la interfaz explícitamente con un miembro de clase (no interfaz) que devuelve la lista en lugar de IEnumerable ...

public class MyData : ISomeData { private List<string> m_MyData = new List<string>(); public List<string> Data { get { return m_MyData; } } #region ISomeData Members IEnumerable<string> ISomeData.Data { get { return Data.AsEnumerable<string>(); } } #endregion }

Editar: para aclarar, esto permite que la clase MyData devuelva una lista cuando se trata como una instancia de MyData; a la vez que le permite devolver una instancia de IEnumerable cuando se trata como una instancia de ISomeData.


Si necesita tener una lista en su interfaz (cantidad conocida de elementos con acceso aleatorio), entonces debería considerar cambiar la interfaz a

public interface ISomeData { ICollection<string> Data { get; } }

Esto le dará tanto la extensibilidad como las características que necesita de una lista.

List<T> no se puede subclasificar fácilmente, lo que significa que puede tener problemas para devolver ese tipo exacto de todas las clases que desean implementar su interfaz.

ICollection<T> por otro lado, se puede implementar de varias maneras.


Yo elegiría la opción 2:

El punto para definir una interfaz en su código es definir un contrato, por lo que usted y otras personas que implementan su interfaz saben en qué ponerse de acuerdo. Si define IEnumerable o List en su interfaz es realmente un problema de contrato y pertenece a la guía de diseño de framework. Aquí hay un libro completo para discutir esto.

Personalmente, exponería IEnumerable e implementaría MyData en IEnumerable, y podría convertirlo a List en el método RandomAccess ().