foreach loop kann den Typ nicht in die von ihm implementierte Schnittstelle umwandeln

8

Bearbeitet mit vollständigem, funktionierendem Code-Beispiel.

In meiner IRC-Anwendung empfängt die Anwendung Inhalte von einem IRC-Server. Der Inhalt wird an eine Factory gesendet, und die Factory spuckt ein Objekt IMessage aus, das von der Präsentationsschicht der Anwendung konsumiert werden kann. Die IMessage -Schnittstelle und eine einzelne Implementierung sind unten gezeigt.

%Vor%

Um das Objekt IMessage zu erhalten, abonniert die Darstellungsschicht Benachrichtigungen, die in meiner Domänenebene veröffentlicht werden. Das Benachrichtigungssystem iteriert über eine Sammlung von Subskribenten auf eine angegebene IMessage Implementierung und löst eine Rückrufmethode für den Subskribenten aus.

%Vor%

Um zu zeigen, wie der obige Code funktioniert, habe ich eine einfache Konsolenanwendung, die Benachrichtigungen vom obigen ServerMessage -Typ abonniert. Die Konsolenanwendung wird zuerst veröffentlicht, indem das Objekt ServerMessage direkt an die Methode Publish<T> übergeben wird. Dies funktioniert ohne Probleme.

Im zweiten Beispiel erstellt die App eine IMessage-Instanz mit einer Factory-Methode. Die IMessage-Instanz wird dann an die Publish<T> -Methode übergeben, wodurch mein Varianzproblem eine InvalidCastException verursacht.

%Vor%

Die Ausnahme besagt, dass ich ein INotification<IMessage> (was die Publish-Methode versteht, versteht die Nachricht, die veröffentlicht wird) nicht in ein INotification<ServerMessage> umwandeln kann.

Ich habe versucht, die INotification-Schnittstelle generisch als kontravariant zu markieren, wie INotification<in TMessageType> , kann dies aber nicht tun, weil ich TMessageType als Parameter für die Callbacks der Register -Methode verwende. Soll ich die Schnittstelle in zwei separate Schnittstellen aufteilen? Eine, die sich registrieren kann und eine, die verbrauchen kann? Ist das die beste Alternative?

Jede zusätzliche Hilfe wäre großartig.

    
Johnathon Sullinger 18.02.2015, 03:22
quelle

3 Antworten

1

Ich habe das gelöst, indem ich eine weitere Ebene der Indirektion hinzugefügt habe. Nach dem Rat von @PeterDuniho habe ich die INotification<TMessage> -Schnittstelle in zwei separate Schnittstellen aufgeteilt. Durch Hinzufügen der neuen INotificationProcessor -Schnittstelle kann ich meine Auflistung von Listenern von ISubscription in INotificationProcessor ändern und dann über meine Sammlung von Listenern als INotificationProcessor iterieren.

%Vor%

Die Implementierung INotificationProcessor implementiert sowohl INotificationProcessor als auch INotification<TMessageType> . Dies ermöglicht der Klasse Notification , die IMessage in den entsprechenden generischen Typ für die Veröffentlichung zu konvertieren.

%Vor%

My NotificationManager kann jetzt eine Sammlung von INotificationProcessor -Typen anstelle von ISubscription enthalten und die Methode ProcessMessage(IMessage) aufrufen, unabhängig davon, ob eine IMessage oder eine ServerMessage eingeht.

%Vor%

Das ursprüngliche App-Beispiel funktioniert jetzt ohne Probleme.

%Vor%

Danke allen für die Hilfe.

    
Johnathon Sullinger 19.02.2015, 16:38
quelle
2

Das grundlegende Problem hier ist, dass Sie versuchen, Ihre Typen auf unterschiedliche Weise zu verwenden, aber das wird nicht für die Syntax unterstützt, die Sie verwenden möchten. Dank Ihres aktualisierten und jetzt vollständigen (und fast minimalen) Codebeispiels ist es auch klar, dass Sie das einfach nicht so machen können, wie Sie es jetzt geschrieben haben.

Die fragliche Schnittstelle und insbesondere die Methode, die Sie verwenden möchten (dh ProcessMessage() , könnte tatsächlich als kovariante Schnittstelle deklariert werden (wenn Sie die Methode Register() in a aufteilen separate Schnittstelle), aber das würde Ihr Problem nicht lösen.

Sie sehen, dass Sie versuchen, einer Variablen, die als INotification<ServerMessage> typisiert ist, eine Implementierung von INotification<IMessage> zuzuweisen. Beachten Sie, dass der Aufrufer, sobald diese Implementierung der Variablen dieses Typs zugewiesen wurde, beliebige Instanz von IMessage an die Methode übergeben kann, selbst wenn es sich nicht um eine Instanz von ServerMessage handelt. Aber die tatsächliche Implementierung erwartet (nein, verlangt!) Eine Instanz von ServerMessage .

Mit anderen Worten, der Code, den Sie gerade schreiben wollen, ist nicht statisch sicher. Es gibt keine Möglichkeit zur Kompilierzeit zu garantieren, dass die Typen übereinstimmen, und das ist einfach nicht etwas, was C # tun möchte.


Eine Option wäre, die Typ-Sicherheit der Schnittstelle zu schwächen, indem sie nicht-generisch gemacht wird. I.e. Lass es immer eine IMessage Instanz akzeptieren. Dann müsste jede Implementierung entsprechend ihren Bedürfnissen umgesetzt werden. Codierungsfehler würden nur zur Laufzeit mit einem InvalidCastException abgefangen, aber korrekter Code würde gut laufen.


Eine andere Option wäre, die Situation so einzustellen, dass der vollständige Typparameter bekannt ist. Zum Beispiel make PushMessage() generische Methode, so dass sie Publish() mit dem Typparameter ServerMessage anstelle von IMessage aufrufen kann:

%Vor%

Auf diese Weise würde der Typparameter T genau in der foreach -Schleife übereinstimmen, in der Sie das Problem haben.


Persönlich bevorzuge ich den zweiten Ansatz. Ich weiß, dass dies in Ihrer aktuellen Implementierung nicht funktioniert. Aber IMHO könnte es sich lohnen, das breitere Design noch einmal zu betrachten, um zu sehen, ob Sie das gleiche Feature unter Beibehaltung des generischen Typs durchsetzen können, so dass es verwendet werden kann, um die Sicherheit beim Kompilieren zu gewährleisten.

    
Peter Duniho 18.02.2015 04:55
quelle
1

Lange dehnen Sie hier, von herumspielen mit dem Code, den Sie zur Verfügung gestellt ...

Kann ich anhand von Haltepunkten wissen, was die Methode für T hält und was der Typ von Listenern [messageType] ist?

%Vor%

Wenn es in der Tat Notification<IMessage> auf der einen Seite und Notification<ServerMessage> auf der anderen Seite ist, dann ist dies ein Kompatibilitätsproblem für die Zuweisung.

Es gibt eine Lösung, aber Sie haben keinen Code für die Erstellung von Benachrichtigungen angezeigt. Ich werde von Ihrer aktuellen Codebasis extrapolieren. Dies sollte alles sein, was Sie brauchen.

%Vor%

Ändern Sie dann den Code so, dass im Wesentlichen Folgendes aufgerufen wird:

%Vor%

Wo Listener [messageType] müssen Inotification sein.

Dies sollte verhindern, dass Ihre Notification to Notification explizit wie der Compiler beschwert werden muss.

Die Magie tritt in der Interface-Deklaration für INotification auf, die in T Schlüsselphrase (schlechte Terminologie, sorry), lässt den Compiler wissen, dass T kontravariant ist (standardmäßig, wenn Sie auslassen, T ist invariant, da die Typen übereinstimmen müssen.

BEARBEITEN : Nach den Kommentaren habe ich die Antwort aktualisiert, um den Code wiederzugeben, der tatsächlich geschrieben wurde, im Gegensatz zu dem, was ich dachte geschrieben wurde. Dies bedeutet hauptsächlich, INotifikation als kontravariant (in T) und nicht als kovariant (out T) zu deklarieren.

    
irysius 18.02.2015 04:40
quelle

Tags und Links