none
Generic-Frage RRS feed

  • Frage

  • Hallo allerseits
     
    ich 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 beliebiges
    Bar<T>-Objekt enthalten soll:
     
       class Baz
       {
           Bar<T> bar;
       }
     
    Das funktioniert natürlich so nicht, weil Baz ja keinen Typ-Parameter hat. Ich habe mir
    jetzt damit beholfen, daß ich ein leeres Interface IBar definiert habe, und nun in Baz
    auf 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?
     
    TIA
     
    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
     

    Freitag, 23. März 2012 08:57

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

    Freitag, 23. März 2012 13:25
    Moderator
  • 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

    Freitag, 23. März 2012 14:51
    Moderator

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).aspx

    Gruß
    Marcel

    Freitag, 23. März 2012 09:33
    Moderator
  • 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 Feld
    kann selbst wieder mehrere Child-Felder enthalten. Die Felder haben ein gemeinsames
    Interface (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 meinem
    Beispiel), wobei T vom Typ IField ist. Die Children-Eigenschaft der Felder verwendet
    ein 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 Implementiereung
    von Record<T>.
     
    Diese Klassen befinden sich alle in einer Klassenbibliothek. Die Applikation, die dann
    mit 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 ParseError
    gespeichert:
     
       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 Verweis
    auf das Record<T>-Objekt enthält, bei dessen Erstellung der Fehler aufgetreten ist.
     
    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
     
    Freitag, 23. März 2012 12:52
  • 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

    Freitag, 23. März 2012 13:25
    Moderator
  • 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 der
    Parser bekommt. Und der erzeugt *immer* ein korrektes Record-Objekt, das Informationen zu
    den eingelesenen Daten enthält und das im Falle eines Fehlers eben nicht mit (allen) Feldern
    befüllt wird.
     
    Die Idee hinter dem Verweis auf Record in ParseError ist, nach dem Parsen eine Liste von
    aufgetretenen Fehlern zu haben und hier direkt zu den jeweiligen Records navigiern zu können
    und 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üße
    Thomas
     
    --
    Any problem in computer science can be solved with another layer
    of indirection. But that usually will create another problem.
                                       David Wheeler
     
    Freitag, 23. März 2012 14:39
  • 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

    Freitag, 23. März 2012 14:51
    Moderator
  • 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  

    Montag, 26. März 2012 12:48
  • 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

    Montag, 26. März 2012 18:15
    Moderator