Pregunta Conversión de genéricos utilizando interfaces


Estoy tratando con una base de código que hace un uso generoso de genéricos con tipos de clases. He estado presionando para usar interfaces (por una variedad de razones). En mi trabajo de convertir lentamente cosas, he golpeado lo siguiente y no puedo superarlo.

En pocas palabras (y mínimamente), el problema que estoy enfrentando es el siguiente:

private static IList<IDictionary<int, string>> exampleFunc()
{
    //The following produces a compile error of:
    // Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.Dictionary<int,string>>' to 
    //  'System.Collections.Generic.IList<System.Collections.Generic.IDictionary<int,string>>'. 
    // An explicit conversion exists (are you missing a cast?)  
    return new List<Dictionary<int, string>>(); 
}

Como se dice en el comentario, produce un error de tiempo de compilación.

¿Cómo puedo producir un objeto que se adhiere a la interfaz que se puede devolver?

En general, ¿cómo puedo convertir o lanzar el tipo "interno"?


Actualizar: Mi ejemplo puede ser engañoso. Mi situación no es específica de una Lista que contiene elementos del Diccionario. Hay ejemplos de eso, pero también podría ser así IList<IVehicle> vehicleList = new List<Car>(); 

El quid de mi pregunta es cómo lidiar con los tipos genéricos mal emparejados.


5
2018-06-05 20:51


origen


Respuestas:


Estas tratando de tratar un IList como si fuera covariante, lo que significa que desea poder tratar todas las instancias del argumento genérico como si fueran de un tipo menos derivado. Eso no funcionará porque IList es invariante. (Imagínese qué pasaría si alguien agregara un SomeOtherTypeOfDictionary<int,string> a la lista que devuelves. Ahora has puesto algo que no es un Dictionary<int,string> en un List<Dictionary<int, string>>.

Podrías devolver un IEnumerable<Dictionary<int, string>> cual es covariante

O bien, si realmente no tiene una lista ya poblada, simplemente cree una lista del tipo apropiado (List<IDictionary<int, string>>) desde el comienzo.

Otra opción es crear un nuevo lista y copia sobre todos los elementos en ella return new List<Dictionary<int, string>>().Cast<IDictionary<int, string>>>().ToList();, aunque esto generalmente no es un buen diseño, especialmente si aún no has completado la lista con datos.

Y todo esto supone que tienes una razón de peso real para tener un IList<IDictionary<int,string>> para empezar. Yo afirmaría que realmente no hay una razón convincente para restringir su List y Dictionary en primer lugar, a las interfaces correspondientes, y que no hay nada de malo en devolver un List<Dictionary<int,string>> a menos que realmente tenga casos de uso para devolver ILists que no sean de lista o IDictionaries que no sean de diccionario. Complica su código, elimina la funcionalidad valiosa para la persona que llama, y ​​realmente no agrega valor si nunca va a devolver diferentes implementaciones de esa interfaz.


5
2018-06-05 20:56



Tu código no funcionará porque IList<T> no es covariante / contravariante.

Si crea la lista usted mismo, puede darle un tipo compatible para comenzar con:

private static IList<IDictionary<int, string>> exampleFunc()
{
    var list = new List<IDictionary<int, string>>();
    list.Add(new Dictionary<int, string>());
    return list;
}

Si eso no es una opción, por ejemplo. Como lo obtienes de una fuente externa, puedes lanzarlo.

private static IList<IDictionary<int, string>> exampleFunc()
{
    var list = new List<Dictionary<int, string>>();
    list.Add(new Dictionary<int, string>());
    return list.Cast<IDictionary<int, string>>().ToList();
}

4
2018-06-05 20:55



List<T> implementos IList<T>, como todos sabemos. Por lo tanto, podemos escribir esto:

IList<int> xs = new List<int>();

Ahora asuma una interfaz I y una clase C, dónde C implementos I. Podemos escribir esto:

I x = new C();

Es un error común combinar estos dos hechos en la creencia de que esto debería ser posible:

IList<I> xs = new List<C>();

¿Por qué esto es ilegal? Dos razones:

Primero, List<C> no implementa IList<I>. Implementa IList<C>.

Segundo, tratar un List<C> como un IList<I> Se rompe el tipo de seguridad. Por ejemplo, considera la clase D, que también implementa la interfaz I. Si pudiéramos asignar un List<C> a IList<I>, entonces podríamos hacer esto:

IList<C> cs = new List<C>();
IList<I> eyes = cs;  //hypothetically legal for this thought experiment
eyes.Add(new D());   //legal because D implements I
C c = cs[0];         //legal, but results in a C variable pointing to a D object!

Para evitar esto, el compilador no permite la segunda línea de código.

Si esto fuera legal, entonces el List<T>.Add(T) El método tendría que verificar el tipo de su argumento, que niega uno de los beneficios primarios del sistema genérico en primer lugar.

La solución a su problema es usar tipos de colección concretos instanciados con argumentos de tipo de interfaz:

private static IList<I> exampleFunc()
{
    return new List<I>();
}

Si obtiene la lista de otro lugar, tiene dos opciones:

  1. Escribe una clase de envoltorio que implementa IList<I> pero tiene una referencia al IList<C>, lanzando objetos (posiblemente levantando excepciones) antes de agregarlos a la lista interna.

  2. Copia los datos en un List<I>.

La única razón para usar la primera solución, que es un poco incómoda, es si necesita que los cambios en la lista se reflejen en alguna otra parte de la aplicación que tenga una referencia al original. List<C>.

Como otros carteles han mencionado, esta pregunta está relacionada con el concepto de covarianza y contravarianza de parámetros de tipo genérico, que le permite escribir IEnumerable<I> xs = new List<C>(); porque IEnumerable<T> es covariante en T. Si quieres entender esto mejor, y por qué List<T> no puede ser covariante en T, es posible que desee comenzar con la excelente serie de Eric Lippert sobre el tema, comenzando con http://blogs.msdn.com/b/ericlippert/archive/2007/10/16/covariance-and-contravariance-in-c-part-one.aspx


1
2018-06-05 23:15



He descubierto un método para trabajar a través de las conversiones de mi interfaz. Aquí está la situación de nuevo, volviendo a mi ejemplo de una lista de diccionarios. (Podría ser cualquier tipo de datos, ya sea incorporado o personalizado).

class Program
{
    private static List<Dictionary<string, Guid>> GetList()
    {
        var myList = new List<Dictionary<string, Guid>>();
        for (var i = 0; i < 25; i++)
        {
            var newDict = new Dictionary<string, Guid>();
            for (var j = 0; j < 25; j++)
            {
                newDict.Add(j.ToString(), Guid.NewGuid());
            }
            myList.Add(newDict);
        }
        return myList;
    }
    //Really, I want the following to use interfaces; lets not worry about why there is a middle-man function.
    private static IList<Dictionary<string, Guid>> MyIntermediateFuncForAReason() 
    {
        var originalList = GetList();
        //whatever processing.
        return originalList;
    }
    static void Main(string[] args)
    {
        var t = MyIntermediateFuncForAReason();
        // whatever...
    }
}

Modificar el MyIntermediateFuncForAReason para convertir el a los tipos deseados, tales como:

private static IList<IDictionary<string, Guid>> MyIntermediateFuncForAReason()
{
    var originalList = GetList();
    //whatever processing.
    return originalList.Select((IDictionary<string, Guid> i) => i).ToList();
}

0
2017-10-22 20:03