2010-11-18 6 views
3

Ich schreibe ein Werkzeug, das mit einer API für ein anderes Stück Software zusammenarbeitet. Ein Teil meines Tools muss Berichte über die verschiedenen über die API gefundenen Objekte generieren, und ich möchte, dass diese Berichte einfache Zeichenfolgen enthalten, die jedes Objekt identifizieren. Standardmäßig möchte ich ToString() verwenden, um die Zeichenfolge für jedes Objekt zu generieren. Wie auch immer, ich habe festgestellt, dass die Standard-ToString() - Implementierungen in dieser API nicht beschreibend sind.Kann ich ToString() für Klassen in einer öffentlichen API überschreiben?

Am Anfang dachte ich an etwas wie den Code unten mit einer langen Switch-Anweisung. Obwohl dies höchstwahrscheinlich unhandlich lange werden würde.

Als nächstes versuchte ich mit Erweiterungsmethoden (siehe den Code unten). CustomObjectDescription() funktionierte wie erwartet, aber als ich versuchte, ToString() aufzurufen, gab es nur die standardmäßigen ToString() - Ergebnisse zurück. Ich habe noch nie Extensionsmethoden verwendet, so dass ich komplett verrückt sein könnte und denke, dass so etwas überhaupt möglich ist.

Ich kann nicht garantieren, dass es eine CustomObjectDescription() -Erweiterung für jeden Typ in der API geben wird. Wenn ich diese Route nehme, müsste ich jedes Mal Reflektion verwenden, um zu überprüfen, ob das aktuelle Objekt a hat GetObjectDescription() Erweiterung. Ich möchte die Reflexion möglichst vermeiden.

public static class APIObjectDescriptionExtensions 
{ 
    public static string ToString(this APIObject element) 
    { 
     return "ElementName = " + element.Name + " ElementID =" + element.Id.IntegerValue.ToString(); 
    } 

    public static string CustomObjectDescription(this APIObject element) 
    { 
     return "ElementName = " + element.Name + " ElementID =" + element.Id.IntegerValue.ToString(); 
    } 
} 

Hat jemand andere Vorschläge, wie ich dieses Problem angehen sollte? Ich würde eine Lösung bevorzugen, bei der der Code für jeden API-Typ unabhängig voneinander ist (keine riesige Switch-Anweisung).

Wenn möglich, möchte ich auch die Beschreibung String-Code für einen Typ zu Untertypen erben, es sei denn, diese Typen haben ihre eigenen eindeutigen Beschreibung String-Code.

Ich denke, es könnte eine bessere Lösung geben, die das Erstellen von benutzerdefinierten TypeConverter oder das Überschreiben/Erweitern von System.Convert.ToString() beinhaltet?


aktualisieren

denke ich, das folgende Beispiel helfen könnte zu klären, was zu tun, ich versuche. Letztendlich möchte ich in der Lage sein, jede beliebige Klasse aus dieser API zu nehmen, deren Typ bis zur Laufzeit nicht bekannt ist, und eine Beschreibung zu generieren. Wenn der Typ meine benutzerdefinierte Erweiterungsmethode hat, sollte sie verwendet werden, ansonsten sollte der Code auf den alten ToString() zurückgreifen.

public static string GetDataDescription(object o) 
    { 
     //get the type of the input object 
     Type objectType = o.GetType(); 

     //check to see if a description extension method is defined 
     System.Reflection.MethodInfo extensionMethod = objectType.GetMethod("MyDescriptionExtensionMethod"); 

     if (extensionMethod != null) 
     { 
      //if a description extension method was found returt the result 
      return (string)extensionMethod.Invoke(o, new object[] { }); 
     } 
     else 
     { 
      //otherwise just use ToString(); 
      return o.ToString(); 
     } 
    } 

Dieser Code oben nicht aber, weil Erweiterungsmethoden funktioniert aren't found by GetMethod().

+0

Sie können eine Instanzmethode nicht mithilfe von Erweiterungsmethoden ausblenden, da Instanzmethoden zuerst überprüft werden, wenn der Compiler versucht, einen Methodenaufrufausdruck zu binden. Einzelheiten finden Sie in der Spezifikation (§7.6.5.1). – jason

+0

Eine Instanzmethode wird immer vor jeder Erweiterungsmethode mit demselben Namen verwendet (alle anderen sind gleich). Die Verwendung von ToString ist also nicht möglich. – BrokenGlass

+0

Mögliches Duplikat von http://StackOverflow.com/Questions/4093501/How-Do-I-Override-Tostring-and-Implement-Generic?RQ=1 –

Antwort

0

Haben Sie versucht, einen anderen Namen als ToString() in Ihrer Erweiterungsklasse zu verwenden? Ich bin auch nicht ganz sicher über Erweiterungsmethoden, aber ich vermute die Base. ToString wurde anstelle von Ihnen aufgerufen. Möglicherweise würde eine ToDescription() - Erweiterung zu besseren Ergebnissen führen.

+0

Ja CustomObjectDescription() wie ich im OC erwähnt, und ja es hat funktioniert. Das Problem dabei ist, dass es keine Möglichkeit gibt zu wissen, dass es für jeden API-Typ eine CustomObjectDescription() -Erweiterungsmethode gibt. –

2

Sie könnten einen Wrapper für jede der Klassen ähnlich wie diese bieten:

public class SomeAPITypeWrapper : SomeAPIType 
    { 
     public override string ToString() 
     { 
      return SomeProperty; 
     } 
    } 

    public class SomeOtherAPITypeWrapper : SomeOtherAPIType 
    { 
     public override string ToString() 
     { 
      return SomeOtherProperty; 
     } 
    } 

Dies ermöglicht es sicherlich für die Klassen Base/Unterklassen wie in Ihrer Frage angefordert. Es hält es auch sauber und innerhalb Ihres Objektmodells selbst statt in einer switch-Anweisung oder Hilfsklasse.

+0

Zunächst wäre ich nicht überrascht, wenn die API-Klassen versiegelt sind. Wenn nicht, wie bekomme ich dann eine Instanz von SomeAPItypeWrapper? Die API wird eine SomeAPIType-Instanz zurückgeben. Ich brauche also immer noch eine konsistente Methode, um zwischen dem wahren API-Typ und der benutzerdefinierten Wrapper-Klasse Typ zu verwenden. –

+0

Ja, aber Sie können dann Komposition statt Vererbung verwenden. –

+0

Dies ist "lokale Erweiterung einführen" - Vorbehalte schließen ein: Sie müssen die Erstellung steuern (abgeleiteten Typ erstellen), wenn eine Instanz des Basistyps erstellt wird. Zweitens: Die Basistypen müssen nicht versiegelt sein. – Gishu

0

Wenn ein bestimmter Methodenaufruf durch eine Instanzmethode und eine Erweiterungsmethode aufgelöst werden kann, wird der Instanzmethode der Vorzug gegeben. Daher müssen Erweiterungsmethoden so benannt werden, dass sie nicht dieselben Namen wie Methoden im erweiterten Typ haben.

Aus dem obigen Code scheint es, dass Sie die Quelle von APIObject und seine Ableitungen nicht steuern. So sind Ihre Optionen ‚Introduce Foreign Method‘ und ‚Introduce Local Extension

  • Ich würde versuchen, ausländische Methode (die C# Erweiterungsmethoden ähnlich ist) .. nicht sicher, warum Sie Reflexion obwohl brauchen würde. Wenn die Erweiterungsmethode nicht vorhanden ist, handelt es sich um einen Kompilierungsfehler. Wie verzehren Sie diese Methode?
  • Schließlich sind Switch-Anweisungen nicht so schlimm ... es sei denn, sie sind sehr lang/müssen häufig geändert/an verschiedenen Orten dupliziert werden.
+0

Es gibt eine dritte Option: Verwenden Sie Cecil, um Code in ihre Bibliothek einzubringen. – Joshua

+0

@Joshua - wow. Nicht sicher, wie das funktioniert ... aber nicht diese gleiche Montage Manipulation. Funktioniert es mit stark typisierten Assemblys und auf anderen Plattformen als Mono/Linux? – Gishu

+0

Ich habe ein mächtiges Werkzeug in meiner Toolbox, um starke Benennungen sogar in Microsoft .NET zu überschreiben, solange es keine Mixed-Mode-Assemblies in der Baumstruktur von der Du zu der überschriebenen gibt. Mono.Cecil und Mono.Security können kompiliert und gegen Microsoft .NET Framework verwendet werden. – Joshua

0

Ich schlage vor, eine Dictionary<Type,Converter<object,string>> zu machen. Dann können Sie nach einem benutzerdefinierten Zeichenfolgen-Generator suchen, und wenn keiner gefunden wird, rufen Sie ToString.

Beachten Sie, dass das Wörterbuch nach einer genauen Übereinstimmung für Typen sucht. Wenn Sie also mit Unterklassen umgehen möchten, müssen Sie zusätzlichen Code schreiben, um zu sehen, ob Basistypen im Wörterbuch aufgeführt sind (Hinweis: Wenn Sie a Basisklasse, oder auch wenn Sie dies nicht tun, fügen Sie den eigentlichen abgeleiteten Typ dem Wörterbuch hinzu, so dass Sie nicht erneut durch den Vererbungsbaum rekursiv werden müssen).

Beachten Sie, dass Sie einen „offenen delegieren“ für Object.ToString() bauen, der dem Converter<object,string> Vertrag entspricht und verwenden, die als Standard, auch speichern Sie es im Wörterbuch, anstatt den Anruf zu ToString Spezial-Gehäuse.

0

Sie könnten die gesamte Stimulation auf ein separates Problem Ihrer Anwendung verschieben. StatePrinter (https://github.com/kbilsted/StatePrinter) ist eine solche API, in der Sie die Standardeinstellungen verwenden oder abhängig von den zu druckenden Typen konfigurieren können.

var car = new Car(new SteeringWheel(new FoamGrip("Plastic"))); 
car.Brand = "Toyota"; 

dann ausdrucken

StatePrinter printer = new StatePrinter(); 
Console.WriteLine(printer.PrintObject(car)); 

und Sie erhalten die folgende Ausgabe

new Car() { 
    StereoAmplifiers = null 
    steeringWheel = new SteeringWheel() 
    { 
     Size = 3 
     Grip = new FoamGrip() 
     { 
      Material = ""Plastic"" 
     } 
     Weight = 525 
    } 
    Brand = ""Toyota"" } 

und mit der IValueConverter Abstraktion können Sie festlegen, wie Typen sind Drucker und mit dem FieldHarvester können Sie festlegen, Welche Felder sollen in die Zeichenfolge aufgenommen werden?

Verwandte Themen