Mein aktueller nicht kompilierter Code ähnelt dem folgenden:
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) { }
}
Der C # -Compiler weigert sich, dies zu kompilieren, und führt die folgende Regel / den folgenden Fehler an:
'MyProject.MyFoo <TA>' kann nicht sowohl 'MyProject.IFoo <TA>' als auch 'MyProject.IFoo <MyProject.B>' implementieren, da sie möglicherweise für einige Typparameter-Ersetzungen vereinheitlicht werden
Ich verstehe, was dieser Fehler bedeutet; Wenn TA
es überhaupt etwas geben könnte, könnte es auch technisch gesehen ein B
Problem sein, das zu Unklarheiten über die beiden verschiedenen Handle
Implementierungen führen würde.
Aber TA kann nichts sein. Basierend auf der Typhierarchie TA
kann es nicht sein B
- zumindest glaube ich nicht, dass es das kann. TA
muss ableiten von A
, was nicht abgeleitet ist B
, und offensichtlich gibt es in C # /. NET keine Vererbung mehrerer Klassen.
Wenn ich die generischen Parameter entfernen und zu ersetzen TA
mit C
, oder sogar A
, kompiliert.
Warum erhalte ich diesen Fehler? Ist es ein Fehler oder eine allgemeine Unintelligenz des Compilers oder fehlt mir noch etwas?
Gibt es eine Problemumgehung oder muss ich die MyFoo
generische Klasse nur als separate nicht generische Klasse für jeden möglichen TA
abgeleiteten Typ erneut implementieren ?
B
das Sie übergebenTA
? </ strike>B
ist kein Typparameter, sondern ein tatsächlicher Typ. Der einzige Typparameter istTA
.Antworten:
Dies ist eine Folge von Abschnitt 13.4.2 der C # 4-Spezifikation, in dem es heißt:
Beachten Sie den zweiten Satz dort.
Es ist daher kein Fehler im Compiler; Der Compiler ist korrekt. Man könnte argumentieren, dass es sich um einen Fehler in der Sprachspezifikation handelt.
Im Allgemeinen werden Einschränkungen in fast jeder Situation ignoriert, in der eine Tatsache über einen generischen Typ abgeleitet werden muss. Einschränkungen werden meistens verwendet, um die zu bestimmen effektive Basisklasse eines generischen Typparameters zu bestimmen, und sonst wenig.
Leider führt dies manchmal zu Situationen, in denen die Sprache unnötig streng ist, wie Sie festgestellt haben.
Es ist im Allgemeinen ein schlechter Codegeruch, "dieselbe" Schnittstelle zweimal zu implementieren, was sich in gewisser Weise nur durch generische Typargumente unterscheidet. Es ist bizarr, zum Beispiel zu haben
class C : IEnumerable<Turtle>, IEnumerable<Giraffe>
- was ist C , dass es sowohl eine Folge von Schildkröten ist, und eine Folge von Giraffen, zugleich ? Können Sie beschreiben, was Sie hier eigentlich versuchen? Es könnte ein besseres Muster geben, um das eigentliche Problem zu lösen.Wenn Ihre Benutzeroberfläche genau so ist, wie Sie es beschreiben:
interface IFoo<T> { void Handle(T t); }
Dann stellt die Mehrfachvererbung der Schnittstelle ein weiteres Problem dar. Sie könnten sich vernünftigerweise dafür entscheiden, diese Schnittstelle kontravariant zu machen:
interface IFoo<in T> { void Handle(T t); }
Angenommen, Sie haben
interface IABC {} interface IDEF {} interface IABCDEF : IABC, IDEF {}
Und
class Danger : IFoo<IABC>, IFoo<IDEF> { void IFoo<IABC>.Handle(IABC x) {} void IFoo<IDEF>.Handle(IDEF x) {} }
Und jetzt wird es richtig verrückt ...
IFoo<IABCDEF> crazy = new Danger(); crazy.Handle(null);
Welche Implementierung von Handle wird aufgerufen? ???
Weitere Gedanken zu diesem Thema finden Sie in diesem Artikel und in den Kommentaren:
http://blogs.msdn.com/b/ericlippert/archive/2007/11/09/covariance-and-contravariance-in-c-part-ten-dealing-with-ambiguity.aspx
quelle
void IFoo<IABC>.Handle(IABC x)
und ich würde vermuten, dass interne Implementierungsdetails herausschauen?IComparable<T>
oderIEquatable<T>
eher als betrachtenIEnumerable<T>
. Es ist durchaus plausibel, ein Objekt zu haben, das mit mehr als einer Art von Typ verglichen werden kann. Tatsächlich bin ich in diesem Fall mehrmals auf Probleme mit der Typvereinigung gestoßen.class Danger : IFoo<IABC>, IFoo<DEF> {...}
erlaubt ist, während sieclass Danger<T1,T2> : IFoo<T1>, IFoo<T2>
immer noch nicht erlaubt ist . Es scheint, dass die Disambiguierung durch ein deterministisches Schiedsverfahren erfolgt, bei dem die erste definierte, spezifischste Implementierung verwendet wird (wo mehrere anwendbar sind).Anscheinend war es beabsichtigt, wie bei Microsoft Connect beschrieben:
Um dieses Problem zu umgehen, definieren Sie eine andere Schnittstelle wie folgt:
public interface IIFoo<T> : IFoo<T> { }
Implementieren Sie dies stattdessen wie folgt:
public class MyFoo<TA> : IIFoo<TA>, IFoo<B> where TA : A { public void Handle(TA a) { } public void Handle(B b) { } }
Es kompiliert jetzt gut, von Mono .
quelle
IIFoo
einabstract class
dann.Sie können es unter das Radar schleichen, wenn Sie einer Basisklasse eine Schnittstelle hinzufügen.
public interface IFoo<T> { } public class Foo<T> : IFoo<T> { } public class Foo<T1, T2> : Foo<T1>, IFoo<T2> { }
Ich vermute, dass dies funktioniert, denn wenn die Typen "vereinheitlichen", ist klar, dass die Implementierung der abgeleiteten Klasse gewinnt.
quelle
Meine Antwort auf im Grunde dieselbe Frage finden Sie hier: https://stackoverflow.com/a/12361409/471129
Bis zu einem gewissen Grad kann dies getan werden! Ich verwende eine Differenzierungsmethode, anstatt Qualifizierer, die die Typen einschränken.
Es vereinheitlicht sich nicht, in der Tat ist es möglicherweise besser als wenn, weil Sie die separaten Schnittstellen auseinander ziehen können.
Siehe meinen Beitrag hier mit einem voll funktionsfähigen Beispiel in einem anderen Kontext. https://stackoverflow.com/a/12361409/471129
Grundsätzlich fügen Sie IIndexer einen weiteren Typparameter hinzu , damit dieser wird
IIndexer <TKey, TValue, TDifferentiator>
.Wenn Sie es dann zweimal verwenden, übergeben Sie "Erste" an die erste Verwendung und "Zweite" an die zweite Verwendung
Klasse Test wird also: Klasse
Test<TKey, TValue> : IIndexer<TKey, TValue, First>, IIndexer<TValue, TKey, Second>
So können Sie tun
new Test<int,int>()
wo erstens und zweitens trivial sind:
interface First { } interface Second { }
quelle
Ich weiß, dass es eine Weile her ist, seit der Thread veröffentlicht wurde, aber für diejenigen, die über die Suchmaschine auf diesen Thread zugreifen, um Hilfe zu erhalten. Beachten Sie, dass 'Base' unten für die Basisklasse für TA und B steht.
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 } } }
quelle
Jetzt raten ...
Könnten A, B und C nicht in externen Assemblys deklariert werden, in denen sich die Typhierarchie nach der Kompilierung von MyFoo <T> ändern kann, was die Welt verwüstet?
Die einfache Problemumgehung besteht darin, nur Handle (A) anstelle von Handle (TA) zu implementieren (und IFoo <A> anstelle von IFoo <TA> zu verwenden). Mit Handle (TA) können Sie ohnehin nicht viel mehr tun als auf Zugriffsmethoden von A (aufgrund der A: TA-Einschränkung).
public class MyFoo : IFoo<A>, IFoo<B> { public void Handle(A a) { } public void Handle(B b) { } }
quelle
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, was ist damit:
public class MyFoo<TA> : IFoo<TA>, IFoo<B> where TA : A { void IFoo<TA>.Handle(TA a) { } void IFoo<B>.Handle(B b) { } }
quelle