parameter - reflection c#
¿Por qué el compilador de C#se queja de que "los tipos pueden unificarse" cuando derivan de diferentes clases base? (6)
Adivinando ahora ...
¿No se pudieron declarar A, B y C en ensamblajes externos, donde la jerarquía de tipos puede cambiar después de la compilación de MyFoo <T>, causando estragos en el mundo?
La solución fácil es simplemente implementar Handle (A) en lugar de Handle (TA) (y usar IFoo <A> en lugar de IFoo <TA>). Con mucho, no puede hacer mucho más con Handle (TA) que acceder a los métodos desde A (debido a la restricción A: TA).
public class MyFoo : IFoo<A>, IFoo<B> {
public void Handle(A a) { }
public void Handle(B b) { }
}
Mi código actual no compilador es similar a esto:
public abstract class A { }
public class B { }
public class C : A { }
public interface IFoo<T>
{
void Handle(T item);
}
public class MyFoo<TA> : IFoo<TA>, IFoo<B>
where TA : A
{
public void Handle(TA a) { }
public void Handle(B b) { }
}
El compilador de C # se niega a compilar esto, citando la siguiente regla / error:
''MyProject.MyFoo <TA>'' no puede implementar ''MyProject.IFoo <TA>'' y ''MyProject.IFoo <MyProject.B>'' porque pueden unificarse para algunas sustituciones de parámetros de tipo
Entiendo lo que significa este error; si TA
puede ser algo, entonces técnicamente también podría ser un B
que introduciría ambigüedad sobre las dos implementaciones de Handle
diferentes.
Pero TA no puede ser nada. En función de la jerarquía de tipos, TA
no puede ser una B
, al menos, no creo que pueda hacerlo. TA
debe derivarse de A
, que no deriva de B
, y obviamente no hay herencia de clase múltiple en C # /. NET.
Si elimino el parámetro genérico y reemplazo TA
con C
, o incluso A
, compila.
Entonces, ¿por qué obtengo este error? ¿Es un error o una falta de inteligencia general del compilador, o hay algo más que me falta?
¿Hay alguna solución alternativa o voy a tener que volver a implementar la clase genérica de MyFoo
como una clase separada no genérica para cada tipo de TA
posible derivado?
Aparentemente fue por diseño como se discutió en Microsoft Connect:
Y la solución alternativa es definir otra interfaz como:
public interface IIFoo<T> : IFoo<T>
{
}
Luego implemente esto en su lugar como:
public class MyFoo<TA> : IIFoo<TA>, IFoo<B>
where TA : A
{
public void Handle(TA a) { }
public void Handle(B b) { }
}
Ahora compila bien, por mono .
Esto es una consecuencia de la sección 13.4.2 de la especificación C # 4, que establece:
Si cualquier posible tipo construido creado a partir de C, después de que los argumentos de tipo se sustituyan en L, porque dos interfaces en L sean idénticas, entonces la declaración de C no es válida. Las declaraciones de restricciones no se tienen en cuenta al determinar todos los posibles tipos construidos.
Tenga en cuenta que la segunda oración allí.
Por lo tanto, no es un error en el compilador; el compilador es correcto Uno podría argumentar que es un defecto en la especificación del lenguaje.
En términos generales, las restricciones se ignoran en casi todas las situaciones en las que se debe deducir un hecho sobre un tipo genérico. Las restricciones se utilizan principalmente para determinar la clase base efectiva de un parámetro de tipo genérico, y poco más.
Desafortunadamente, eso a veces lleva a situaciones en las que el lenguaje es innecesariamente estricto, como habrás descubierto.
En general, es un mal olor de código implementar dos veces la misma interfaz, de alguna manera se distingue solo por argumentos de tipo genérico. Es extraño, por ejemplo, tener la class C : IEnumerable<Turtle>, IEnumerable<Giraffe>
- ¿qué es C que es tanto una secuencia de tortugas, como una secuencia de jirafas, al mismo tiempo ? ¿Puedes describir lo que estás tratando de hacer aquí? Puede haber un patrón mejor para resolver el problema real.
Si de hecho tu interfaz es exactamente como la describes:
interface IFoo<T>
{
void Handle(T t);
}
Entonces, la herencia múltiple de la interfaz presenta otro problema. Puede razonablemente decidir hacer esta interfaz contravariante:
interface IFoo<in T>
{
void Handle(T t);
}
Ahora supongamos que tienes
interface IABC {}
interface IDEF {}
interface IABCDEF : IABC, IDEF {}
Y
class Danger : IFoo<IABC>, IFoo<IDEF>
{
void IFoo<IABC>.Handle(IABC x) {}
void IFoo<IDEF>.Handle(IDEF x) {}
}
Y ahora las cosas se ponen realmente locas ...
IFoo<IABCDEF> crazy = new Danger();
crazy.Handle(null);
¿Qué implementación de Handle se llama ???
Vea este artículo y los comentarios para más ideas sobre este tema:
Hmm, ¿qué hay de esto?
public class MyFoo<TA> : IFoo<TA>, IFoo<B>
where TA : A
{
void IFoo<TA>.Handle(TA a) { }
void IFoo<B>.Handle(B b) { }
}
Sé que ha pasado un tiempo desde que se publicó el hilo, pero para aquellos que vienen a este hilo a través del motor de búsqueda para obtener ayuda. Tenga en cuenta que ''Base'' representa la clase base para TA y B a continuación.
public class MyFoo<TA> : IFoo<Base> where TA : Base where B : Base
{
public void Handle(Base obj)
{
if(obj is TA) { // TA specific codes or calls }
else if(obj is B) { // B specific codes or calls }
}
}
Vea mi respuesta básicamente a la misma pregunta aquí: https://.com/a/12361409/471129
Hasta cierto punto, esto se puede hacer! Utilizo un método de diferenciación, en lugar de cualificador (es) que limita los tipos.
No se unifica, de hecho podría ser mejor que si lo hiciera porque puedes separar las interfaces separadas.
Ver mi publicación aquí, con un ejemplo completamente funcional en otro contexto. https://.com/a/12361409/471129
Básicamente, lo que hace es agregar otro parámetro de tipo a IIndexer , de modo que se convierta en IIndexer <TKey, TValue, TDifferentiator>
.
Luego, cuando lo usa dos veces, pasa "Primero" al primer uso y "Segundo" para el segundo uso
Entonces, la prueba de clase se convierte en: clase Test<TKey, TValue> : IIndexer<TKey, TValue, First>, IIndexer<TValue, TKey, Second>
Por lo tanto, puede hacer new Test<int,int>()
donde Primero, y Segundo son triviales:
interface First { }
interface Second { }