Hoy encontré un problema de compilación que me desconcertaba. Considere estas dos clases de contenedor.Genéricos, herencia y resolución de métodos fallidos del compilador de C#
public class BaseContainer<T> : IEnumerable<T>
{
public void DoStuff(T item) { throw new NotImplementedException(); }
public IEnumerator<T> GetEnumerator() { }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { }
}
public class Container<T> : BaseContainer<T>
{
public void DoStuff(IEnumerable<T> collection) { }
public void DoStuff <Tother>(IEnumerable<Tother> collection)
where Tother: T
{
}
}
Los primero define DoStuff(T item)
y los segundos sobrecargas de TI con DoStuff <Tother>(IEnumerable<Tother>)
específicamente para moverse por la ausencia de covariance/contravariance de C# (hasta 4 escucho).
Este código
Container<string> c = new Container<string>();
c.DoStuff("Hello World");
realiza un error de compilación bastante extraño. Tenga en cuenta la ausencia de <char>
de la llamada a método.
El tipo char 'no se puede utilizar como parámetro de tipo 'Tother' en el tipo genérico o método 'Container.DoStuff (System.Collections.Generic.IEnumerable)'. No hay conversión de boxeo de 'char' a 'cadena'.
En esencia, el compilador está tratando de atascar mi llamada a DoStuff(string)
en Container.DoStuff<char>(IEnumerable<char>)
PORQUE string
implementos IEnumerable<char>
, en lugar de utilizar BaseContainer.DoStuff(string)
.
La única forma que he encontrado para hacer esta compilación es añadir DoStuff(T)
a la clase derivada
public class Container<T> : BaseContainer<T>
{
public new void DoStuff(T item) { base.DoStuff(item); }
public void DoStuff(IEnumerable<T> collection) { }
public void DoStuff <Tother>(IEnumerable<Tother> collection)
where Tother: T
{
}
}
¿Por qué es el compilador tratando de atascar una cadena como IEnumerable<char>
cuando: 1) se sabe que puede' t (dada la presencia de un error de compilación) y 2) tiene un método en la clase base que compila bien? ¿Estoy malentendiendo algo sobre genéricos o cosas de métodos virtuales en C#? ¿Hay alguna otra solución que no sea agregar un new DoStuff(T item)
al Container
?
acepto esto parece raro, pero es correcta de acuerdo con la especificación. Esto es consecuencia de la interacción de dos reglas: (1) la verificación de la aplicabilidad de la resolución de sobrecarga ocurre ANTES de la verificación de restricciones, y (2) los métodos aplicables en las clases derivadas son SIEMPRE mejores que los métodos aplicables en las clases base. Ambas son reglas razonablemente sensatas; simplemente interactúan particularmente mal en tu caso. –
Para detalles, consulte la sección 7.5.5.1, específicamente los bits que dicen: (1) "Si el mejor método es un método genérico, los argumentos de tipo (suministrados o inferidos) se comparan con las restricciones ..." y (2) " el conjunto de métodos candidatos se reduce para contener solo métodos de los tipos más derivados ... " –
En última instancia, su problema aquí es un problema de diseño. Está sobrecargando un método "DoStuff" para que signifique "hacer cosas en un único valor de tipo T" y "hacer cosas en una secuencia de valores de tipo T". Esto entra en graves problemas de "resolución de intención" de muchas maneras, por ejemplo, cuando el "tipo T" es en sí mismo una secuencia. Encontrará que las clases de colección existentes en el BCL se han diseñado cuidadosamente para evitar este problema; Los métodos que toman un ítem se llaman "Frob", los métodos que toman una secuencia de ítems se llaman "FrobRange", por ejemplo "Add" y "AddRange" en las listas. –