Benutzer mit den meisten Antworten
Generic-Frage

Frage
-
Hallo allerseitsich habe eine generische Klasse, die von einer anderen generischen Klasse erbt:class Foo<T> { /* Implementation goes here */ }class Bar<T> : Foo<T> { /* Implementation goes here */ }Nun habe ich eine weitere, diesmal nicht-generische Klasse, die ein beliebigesBar<T>-Objekt enthalten soll:class Baz{Bar<T> bar;}Das funktioniert natürlich so nicht, weil Baz ja keinen Typ-Parameter hat. Ich habe mirjetzt damit beholfen, daß ich ein leeres Interface IBar definiert habe, und nun in Bazauf das Interface verweise:interface IBar {}class Bar<T> : Foo<T>, IBar { /* Implementation goes here */ }class Baz{IBar bar;}Das funktioniert zwar, besonders elegant finde ich so ein leeres Interface aber nicht.Gibt es hier eine schönere Lösung?TIAGrüßeThomas--Any problem in computer science can be solved with another layerof indirection. But that usually will create another problem.David Wheeler
- Bearbeitet Thomas Schremser Freitag, 23. März 2012 09:06
Antworten
-
Hallo Thomas,
Du hast ein leeres Interface definiert, interface IBar {}, nur um sicherzustellen, dass Du einen Verweis auf ein Objekt vom Typ Bar<T> in der Klasse Baz speichern kannst? - Das ist nicht sehr viel mehr als wenn Du das Feld als Object definiert hättest: Die Runtimetype-Infos sind ja zur Laufzeit auch für Object immer vorhanden, die Schnittstellenmarkierung sorgt lediglich dafür, dass zur Kompilierzeit etwas mehr Typsicherheit erreicht wird. Wenn Du wenigstens die Schnittstelle voll definiert hättest, z.B. IRecord, dann würde das für mich eher Sinn machen.
Aber die ganze Architektur scheint mir etwas wackelig, IRecord hin oder her. Ein Fehler, auch ein Parse-Fehler, sollte niemals einen direkten Verweis auf das Objekt, das den Fehler verursacht hat, enthalten. Es sei denn, man kann garantieren, dass dieses Objekt immer in einem konsistenten, nutzbaren Zustand nach dem Fehlerereignis verbleibt. Ist das nicht möglich, sollte man einen Benachrichtigungsmechanismus mit dependency inversion verwenden: Anstatt ParseError mit Record zu parametrisieren, könntest Du Record mit ParseError parametrisieren (hier würde ein IParseError-Feld für mich mehr Sinn machen). Im Fehlerfall würde dann Record alle benötigten Fehlerinfos direkt/indirekt in eigens geschaffene Eigenschaften von IParseError hineinschreiben und einen Fehlerzustand signalisieren. Brauchst Du viel Dynamik, kannst Du zusätzlich IParseError mit einem Dictionary versehen, das potentiell unbegrenzte Informationsspeicherung ermöglicht.Gruß
Marcel- Als Antwort markiert Thomas Schremser Montag, 26. März 2012 12:48
-
Hallo Thomas,
Der Parser erzeugt immer ein korrektes Objekt. Das ist schon mal hervorragend. Dann könntest Du ja in diesem Objekt deinen Fehler speichern und den ReportError-Subscriber benachrichtigen, dass in diesem Objekt ein Fehler gespeichert wurde. Der Subscriber speichert darauf den Verweis auf den IErrorProvider (der über handler(this) übergeben wurde) und kann wannimmer IErrorProvider.Error aufrufen, um Details abzurufen. Was Du nun in IError speicherst, ist Sache deiner Anwendung: Es könnte außer dem Fehlertext eine ID oder was auch immer sein, das im konkreten Kontext hilfreich ist:
class Record<T> : IErrorProvider { IError error = null; public void DoSomethingWrong() { SetError("Ganz bööhser Fehler..."); } #region IErrorProvider-Implementation public event EventHandler ReportError; public IError Error { get { return this.error; } } private void SetError(string errorMessage) { this.error = ErrorFactory.CreateError(errorMessage); EventHandler handler = ReportError; if (handler != null) handler(this, EventArgs.Empty); } #endregion }
Gruß
Marcel- Bearbeitet Marcel RomaModerator Freitag, 23. März 2012 15:00
- Als Antwort markiert Thomas Schremser Montag, 26. März 2012 12:48
Alle Antworten
-
Hallo Thomas,
Was soll die leere Schnittstelle (Markierungsschnittstelle) bringen? - Wenn Du in der Klasse Baz ein Objekt vom Typ Bar<T> verwenden mußt, warum verwendest Du dann nicht eine generische Methode?
class Baz { public void DoSomethingWith<T>(T t) { Bar<T> bar = new Bar<T>(); bar.DoSomethingWith(t); } }
Vielleicht erklärst Du aber detaillierter was Du vorhast.Generische Methoden (C#-Programmierhandbuch):
http://msdn.microsoft.com/de-de/library/twcad0zb(v=vs.100).aspxGruß
Marcel -
Hallo Marcel!Marcel Roma schrieb:> Was soll die leere Schnittstelle (Markierungsschnittstelle) bringen?Gar nix, außer daß Baz keinen Type-Parameter braucht ;-)> Wenn Du in der Klasse Baz ein Objekt vom Typ Bar<T> verwenden mußt, warum verwendest> Du dann nicht eine generische Methode?Weil Baz gar nichts mit Bar<T> tut, außer einen Verweis daruf zu halten.> Vielleicht erklärst Du aber detaillierter was Du vorhast.Meine Applikation verarbeitet Datensätze, die aus einzelnen Feldern bestehen. Ein Feldkann selbst wieder mehrere Child-Felder enthalten. Die Felder haben ein gemeinsamesInterface (IField), je nach Datenquelle werden Felder eines konkreten Typs verarbeitet.Für die Felder gibt es eine eigene List-Klasse Fields<T> (entspricht Foo<T> in meinemBeispiel), wobei T vom Typ IField ist. Die Children-Eigenschaft der Felder verwendetein Objekt dieser Klasse.Von Fields<T> wird wiederum eine abstrakte Klasse Record<T> abgeleitet (entspricht Bar<T>),zu jeder konkreten Implementierung von IField gibt es eine entsprechende Implementiereungvon Record<T>.Diese Klassen befinden sich alle in einer Klassenbibliothek. Die Applikation, die dannmit dieser Klassenbibliothek arbeitet enthält dann unter anderem eine Parser-Klasse,die Datensätzte z.B. aus einer Datei liest und in einer List<Record<T>> zurückliefert.Treten beim Einlesen Fehler auf, werden diese in Form von Objekten der Klasse ParseErrorgespeichert:class ParseError{public int ErrorNumber { get; private set; }public string Message { get; private set; }public ParseError(int errorNumber, string message){this.ErrorNumber = errorNumber;this.Message = message;}}Diese Klasse - sie enspricht Baz - möchte ich nun so erweitern, daß sie einen Verweisauf das Record<T>-Objekt enthält, bei dessen Erstellung der Fehler aufgetreten ist.GrüßeThomas--Any problem in computer science can be solved with another layerof indirection. But that usually will create another problem.David Wheeler
-
Hallo Thomas,
Du hast ein leeres Interface definiert, interface IBar {}, nur um sicherzustellen, dass Du einen Verweis auf ein Objekt vom Typ Bar<T> in der Klasse Baz speichern kannst? - Das ist nicht sehr viel mehr als wenn Du das Feld als Object definiert hättest: Die Runtimetype-Infos sind ja zur Laufzeit auch für Object immer vorhanden, die Schnittstellenmarkierung sorgt lediglich dafür, dass zur Kompilierzeit etwas mehr Typsicherheit erreicht wird. Wenn Du wenigstens die Schnittstelle voll definiert hättest, z.B. IRecord, dann würde das für mich eher Sinn machen.
Aber die ganze Architektur scheint mir etwas wackelig, IRecord hin oder her. Ein Fehler, auch ein Parse-Fehler, sollte niemals einen direkten Verweis auf das Objekt, das den Fehler verursacht hat, enthalten. Es sei denn, man kann garantieren, dass dieses Objekt immer in einem konsistenten, nutzbaren Zustand nach dem Fehlerereignis verbleibt. Ist das nicht möglich, sollte man einen Benachrichtigungsmechanismus mit dependency inversion verwenden: Anstatt ParseError mit Record zu parametrisieren, könntest Du Record mit ParseError parametrisieren (hier würde ein IParseError-Feld für mich mehr Sinn machen). Im Fehlerfall würde dann Record alle benötigten Fehlerinfos direkt/indirekt in eigens geschaffene Eigenschaften von IParseError hineinschreiben und einen Fehlerzustand signalisieren. Brauchst Du viel Dynamik, kannst Du zusätzlich IParseError mit einem Dictionary versehen, das potentiell unbegrenzte Informationsspeicherung ermöglicht.Gruß
Marcel- Als Antwort markiert Thomas Schremser Montag, 26. März 2012 12:48
-
Hallo Marcel!> Du hast ein leeres Interface definiert, interface IBar {}, nur um sicherzustellen, dass Du> einen Verweis auf ein Objekt vom Typ Bar<T> in der Klasse Baz speichern kannst? - Das ist> nicht sehr viel mehr als wenn Du das Feld als Object definiert hättest:Ja, und es gefällt mir nicht - darum auch meine Frage, ob's irgendwie anders geht.> Aber die ganze Architektur scheint mir etwas wackelig, IRecord hin oder her. Ein Fehler,> auch ein Parse-Fehler, sollte niemals einen direkten Verweis auf das Objekt, das den Fehler> verursacht hat, enthalten. Es sei denn, man kann garantieren, dass dieses Objekt immer in> einem konsistenten, nutzbaren Zustand nach dem Fehlerereignis verbleibt.In meinem Fall ist Record nicht die Ursache des Fehlers - die liegt in den Daten, die derParser bekommt. Und der erzeugt *immer* ein korrektes Record-Objekt, das Informationen zuden eingelesenen Daten enthält und das im Falle eines Fehlers eben nicht mit (allen) Feldernbefüllt wird.Die Idee hinter dem Verweis auf Record in ParseError ist, nach dem Parsen eine Liste vonaufgetretenen Fehlern zu haben und hier direkt zu den jeweiligen Records navigiern zu könnenund nicht über sämtliche Records iterieren und auf etwaige Fehler prüfen zu müssen.Trotzdem werd' ich mein Konzept nochmal überdenken. Danke jedenfalls für Deinen Input.GrüßeThomas--Any problem in computer science can be solved with another layerof indirection. But that usually will create another problem.David Wheeler
-
Hallo Thomas,
Der Parser erzeugt immer ein korrektes Objekt. Das ist schon mal hervorragend. Dann könntest Du ja in diesem Objekt deinen Fehler speichern und den ReportError-Subscriber benachrichtigen, dass in diesem Objekt ein Fehler gespeichert wurde. Der Subscriber speichert darauf den Verweis auf den IErrorProvider (der über handler(this) übergeben wurde) und kann wannimmer IErrorProvider.Error aufrufen, um Details abzurufen. Was Du nun in IError speicherst, ist Sache deiner Anwendung: Es könnte außer dem Fehlertext eine ID oder was auch immer sein, das im konkreten Kontext hilfreich ist:
class Record<T> : IErrorProvider { IError error = null; public void DoSomethingWrong() { SetError("Ganz bööhser Fehler..."); } #region IErrorProvider-Implementation public event EventHandler ReportError; public IError Error { get { return this.error; } } private void SetError(string errorMessage) { this.error = ErrorFactory.CreateError(errorMessage); EventHandler handler = ReportError; if (handler != null) handler(this, EventArgs.Empty); } #endregion }
Gruß
Marcel- Bearbeitet Marcel RomaModerator Freitag, 23. März 2012 15:00
- Als Antwort markiert Thomas Schremser Montag, 26. März 2012 12:48
-
Hallo Macel!
Ich hab's jetzt so gelöst, daß ich - wie Du vorgeschlagen hast - den Fehler in einer Auflistung in Record<T> (Auflistung deswegen, weil in der späteren Verarbeitung noch welche dazukommen können) gespeichert und statt der ursprünlgich angedachten Liste mit Fehlern erzeuge ich jetzt eine mit fehlerhaften Record<T>'s. Danke nochmal fürs Auflösen des Knotens in meinem Hirn ;-)
class Record<T> : IErrorProvider { IError error = null; public void DoSomethingWrong() { SetError("Ganz bööhser Fehler..."); } #region IErrorProvider-Implementation public event EventHandler ReportError; public IError Error { get { return this.error; } } private void SetError(string errorMessage) { this.error = ErrorFactory.CreateError(errorMessage); EventHandler handler = ReportError; if (handler != null) handler(this, EventArgs.Empty); } #endregion }
Und jetzt muß ich mich outen: ich hab' keine Ahnung, woher das Interface IErrorProvider kommt - in der MSDN find' ich es jedenfalls nicht
Grüße Thomas
--
Any problem in computer science can be solved with another layer of indirection. But that usually will create another problem.
David Wheeler
-
Hallo Thomas,
Ich hab' keine Ahnung, woher das Interface IErrorProvider kommt ...
Sorry. Ich wollte das Ganze etwas entkoppeln; deshalb habe ich mir die Freiheit genommen, diese Schnittstellen zu erfinden. Nimm also den Pseudocode von oben nur als Projektionsfläche für deine Phantasie und Kreativität.
Hier den "Rohrschachtest" in Worten: Record<T> hält eine Referenz auf ein IError-Objekt (benutzerdefinierte Schnittstelle). Nun könnte man das IError-Objekt über den Konstruktor oder über eine Property setzen, im obigen Beispiel habe ich mich aber für eine Factory entschieden. So ist das Error-Objekt völlig von Record<T> losgelöst und kann beliebig erweitert werden, solange es die Schnittstelle behält. Das Error-Objekt wird im obigen Code auch nicht per EventArgs an evtl. Subscriber für das ReportError-Ereignis weitergeleitet, sondern einfach nur benachrichtigt, das ein Fehler vorliegt. Man könnte/sollte sogar das this-Argument aus dem Aufruf des Handlers entfernen und durch eine schlichte Error.GUID ersetzen. - Die interessierten Subscriber werden also benachrichtigt und können das Error-Objekt - so sie Interesse daran haben - leicht wiederfinden. Denn: ErrorFactory.CreateError() könnte nicht nur ein Error-Objekt erstellen und zurückgeben, die Methode könnte das Error-Objekt gleich in ein IList<T> speichern und damit für alle potentiellen Interessenten bereithalten. Diese könnten dann z.B. über ErrorFactory.GetError(GUID) das Fehlerobjekt erhalten und weiter bearbeiten/anzeigen. Man kann beliebig weiterspinnen, aber ich hoffe, dass ich mich hiermit etwas verständlicher ausgedrückt habe.Gruß
Marcel