Mi código de no compilación actual 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 tanto '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
pudiera ser cualquier cosa, técnicamente también podría ser un B
que introduciría ambigüedad sobre las dos Handle
implementaciones diferentes .
Pero TA no puede ser nada. Según la jerarquía de tipos, TA
no puede ser B
, al menos, no creo que pueda. TA
debe derivar de A
, que no deriva de B
, y obviamente no hay herencia de clases múltiples en C # / .NET.
Si quito el parámetro genérico y sustituir TA
con C
, o incluso A
, que se compila.
Entonces, ¿por qué obtengo este error? ¿Es un error o falta de inteligencia general del compilador, o hay algo más que me falta?
¿Hay alguna solución alternativa o simplemente tendré que volver a implementar la MyFoo
clase genérica como una clase no genérica separada para cada TA
tipo derivado posible ?
B
cuando pasasTA
? </strike>B
no es un parámetro de tipo, es un tipo real. El único parámetro de tipo esTA
.Respuestas:
Esto es una consecuencia de la sección 13.4.2 de la especificación C # 4, que establece:
Tenga en cuenta esa segunda oración allí.
Por tanto, no es un error del compilador; el compilador es correcto. Se podría argumentar que es una falla 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 conduce a situaciones en las que el lenguaje es innecesariamente estricto, como ha descubierto.
En general, es un mal olor de código implementar "la misma" interfaz dos veces, de alguna manera distinguida sólo por argumentos de tipo genérico. Es extraño, por ejemplo, tener
class C : IEnumerable<Turtle>, IEnumerable<Giraffe>
: ¿qué es C que es tanto una secuencia de tortugas como una secuencia de jirafas al mismo tiempo ? ¿Puede describir lo que realmente está tratando de hacer aquí? Puede haber un patrón mejor para resolver el problema real.Si, de hecho, su interfaz es exactamente como la describe:
interface IFoo<T> { void Handle(T t); }
Entonces, la herencia múltiple de la interfaz presenta otro problema. Podría razonablemente decidir hacer que esta interfaz sea contravariante:
interface IFoo<in T> { void Handle(T t); }
Ahora suponga que tiene
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 ???
Consulte este artículo y los comentarios para obtener más ideas sobre este tema:
http://blogs.msdn.com/b/ericlippert/archive/2007/11/09/covariance-and-contravariance-in-c-part-ten-dealing-with-ambiguity.aspx
fuente
IComparable<T>
o enIEquatable<T>
lugar deIEnumerable<T>
. Es bastante plausible tener un objeto que pueda compararse con más de un tipo de tipo ... de hecho, me he encontrado con problemas de unificación de tipos varias veces para este caso exacto.class Danger : IFoo<IABC>, IFoo<DEF> {...}
es decir, está permitida, mientras queclass Danger<T1,T2> : IFoo<T1>, IFoo<T2>
todavía no está permitida. Parece que la desambiguación se realiza mediante un arbitraje determinista, donde se utiliza la primera implementación más específica definida (donde se aplican múltiples).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 se compila bien, por mono .
fuente
IIFoo
unabstract class
then.Puedes esconderlo por debajo del radar si pones una interfaz en una clase base.
public interface IFoo<T> { } public class Foo<T> : IFoo<T> { } public class Foo<T1, T2> : Foo<T1>, IFoo<T2> { }
Sospecho que esto funciona porque si los tipos "unifican" está claro que la implementación de la clase derivada gana.
fuente
Vea mi respuesta a básicamente la misma pregunta aquí: https://stackoverflow.com/a/12361409/471129
Hasta cierto punto, ¡esto se puede hacer! Utilizo un método de diferenciación, en lugar de calificadores que limitan los tipos.
No se unifica, de hecho, podría ser mejor que si lo hiciera porque puede separar las interfaces separadas.
Vea mi publicación aquí, con un ejemplo completamente funcional en otro contexto. https://stackoverflow.com/a/12361409/471129
Básicamente, lo que haces es agregar otro parámetro de tipo a IIndexer , para que se convierta en
IIndexer <TKey, TValue, TDifferentiator>
.Luego, cuando lo usa dos veces, pasa "Primero" al primer uso y "Segundo" al 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 First y Second son triviales:
interface First { } interface Second { }
fuente
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 en busca de ayuda. Tenga en cuenta que 'Base' significa 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 } } }
fuente
Adivinando ahora ...
¿No podrían declararse A, B y C en ensamblajes externos, donde la jerarquía de tipos puede cambiar después de la compilación de MyFoo <T>, provocando estragos en el mundo?
La solución sencilla es implementar Handle (A) en lugar de Handle (TA) (y usar IFoo <A> en lugar de IFoo <TA>). No puede hacer mucho más con Handle (TA) que los métodos de acceso desde A (debido a la restricción A: TA) de todos modos.
public class MyFoo : IFoo<A>, IFoo<B> { public void Handle(A a) { } public void Handle(B b) { } }
fuente
Couldn't A, B and C be declared in outside assemblies, where the type hierarchy may change after the compilation of MyFoo<T>, bringing havoc into the world?
.Hmm, ¿qué pasa con esto?
public class MyFoo<TA> : IFoo<TA>, IFoo<B> where TA : A { void IFoo<TA>.Handle(TA a) { } void IFoo<B>.Handle(B b) { } }
fuente