2017-04-25 5 views
0

Angenommen, ich eine Klasse wie diese:Wie verwende ich C# -Attribute, um eine Methode in einer Klasse basierend auf einer Zeichenfolge auszuwählen?

public class MyMethods 
{ 

    [SpecialMethod("test")] 
    public string GetTestString(int i) 
    { 
     return string.Format("Hello world {0} times!",i); 
    } 

    [SpecialMethod("lorem")] 
    public string GetSomeLoremIpsumText(int i) 
    { 
     // ignores the 'i' variable 
     return "Lorem ipsum dolor sit amet"; 
    } 

    // ... more methods with the same signature here ... 

    public string DefaultMethod(int i) 
    { 
     return "The default method happened! The attribute wasn't found."; 
    } 

    public string ThisMethodShouldNotShowUpViaAttributes(int i) 
    { 
     return "You should not be here."; 
    } 
} 

Ich habe auch das Attribut einfach wie folgt definiert:

[AttributeUsage(AttributeTargets.Method)] 
public class SpecialMethodAttribute : System.Attribute 
{ 
    private string _accessor; 
    public string Accessor 
    { 
     get 
     { 
      return _accessor; 
     } 
    } 
    public SpecialMethodAttribute(string accessor) 
    { 
     _accessor = accessor; 
    } 
} 

Was ich so in der Lage sein zu tun aussehen könnte:

public class MethodAccessViaAttribute 
{ 
    private MyMethods _m; 

    public MethodAccessViaAttribute() 
    { 
     _m = new MyMethods(); 
    } 

    public string CallMethodByAccessor(string accessor, int i) 
    { 
     // this is pseudo-code, expressing what I want to be able to do. 
     Func<int, string> methodToCall = FindAMethodByAttribute(_m, accessor); 
     if (methodToCall == null) 
      return _m.DefaultMethod(i); 
     else 
      return methodToCall(i); 
    } 

    public void Test() 
    { 
     // should print "Hello world 3 times!" 
     Console.WriteLine(CallMethodByAccessor("test",3)); 

     // should print "Lorem ipsum dolor sit amet" 
     Console.WriteLine(CallMethodByAccessor("lorem",int.MaxValue)); 

     // should print "The default method happened! The attribute wasn't found." 
     Console.WriteLine(CallMethodByAccessor("I-do-not-exist",0)); 
    } 
} 

Beachten Sie, dass alle Methoden der SpecialMethod die gleiche Methode Unterschrift folgen Attribut. Idealerweise würde die Suchfunktion Methoden ausschließen, die nicht mit der Signatur übereinstimmen, da ein try/catch verwendet werden könnte, um zu testen, ob die Methode mit der Signatur Func übereinstimmt.

Kann ich einen Punkt in der richtigen Richtung, wie dies zu erreichen?

+0

Es klingt für mich wie Sie diesen Ansatz überdenken sollten. Sie könnten auch einen Switch Case in einer einzigen Methode basierend auf dem "Accessor" Wort stecken. Sie können dies auf verschiedene Arten anders als durch Attribute erreichen. Ich bin neugierig, was ist der Anwendungsfall für solchen Code? – JuanR

+0

Ich schreibe eine Bibliothek, die ich in ein anderes Projekt importieren möchte. Daher ist die Hardcodierung der Fälle nicht akzeptabel. Ich möchte in der Lage sein, eine neue mögliche Methode einfach hinzuzufügen, indem ich eine neue Methode mit der richtigen Signatur und dem entsprechenden Attribut hinzufüge. Da .NETs eigenes Komponententest grundsätzlich einen Ansatz wie diesen verwendet, der Methoden bezeichnet, die mit [TestMethod] aufgerufen werden sollten, nehme ich an, dass es einen Weg gibt, dies zu erreichen. – fdmillion

+0

Ich schlage vor, dass Sie in Schnittstellen schauen (Denken Sie Plugin-Muster). Ich würde eine Schnittstelle erstellen, die das Objekt darstellt, das die betreffende Methode implementiert. Ich kann dann die Assembly laden und nach Klassen suchen, die diese Schnittstelle implementieren, sie instanziieren und die Methode aufrufen. – JuanR

Antwort

0

Also habe ich herausgefunden, wie diese Aufgabe zu erledigen ist.

habe ich eine Klasse, die alle Klassen, die Methoden enthalten wird von erben. Ich habe dann eine private Methode erstellt, die tatsächlich Func Objekte extrahiert und erzeugt.

Wir brauchen ein Verfahren, das für jede Methode suchen, welche die gewünschte Eigenschaft aufweist. Reflection hat eine MethodInfo Klasse, die eine Methode darstellt. Wir können einige LINQ verwenden, um alle Methoden der Klasse zu durchsuchen.Wir verwenden dann ein wenig Ausdruck Baum Spaß, das Func Objekt zu erzeugen, die tatsächlich auf diese spezifische Instanz der Klasse die Methode nennen:

// Locates a method on this class that has the SpecialMethod attribute of the given name 
private Func<int, string> FindMethodByAccessor(string accessor) 
{ 
    // Find all methods containing the attribute 
    var desiredMethod = this.GetType().GetMethods() 
       .Where(x => x.GetCustomAttributes(typeof(SpecialMethod), false).Length > 0) 
       .Where(y => (y.GetCustomAttributes(typeof(SpecialMethod), false).First() as SpecialMethod).Accessor == accessor) 
       .FirstOrDefault(); 

    if (desiredMethod == null) return null; 

    // This parameter is the first parameter passed into the method. In this case it is an int.  
    ParameterExpression x = Expression.Parameter(typeof(int)); 
    // This parameter refers to the instance of the class. 
    ConstantExpression instance = Expression.Constant(this); 
    // This generates a piece of code and returns it in a Func object. We effectively are simply calling the method. 
    return Expression.Lambda<Func<int, string>>(
     Expression.Call(instance, desiredMethod, new Expression[] { x }), x).Compile(); 

} 

Die Idee der Ausdruck Bäume ist mir noch neu, aber diese Funktion genau das tut, Was ich will - es wird eine Methode für die Instanz der Klasse finden, in der es läuft, die mit dem angegebenen Attribut versehen wurde und es als Func Objekt zurückgibt.

Wir können nun das Verfahren beenden, die eigentlich die anderen Methoden aufrufen:

public string CallMethodByAccessor(string accessor, int i) 
{ 
    Func<int, string> methodToCall = FindMethodByAccessor(accessor); 

    if (methodToCall == null) 
     return DefaultMethod(i); 
    else 
     return methodToCall(i); 
} 

public string DefaultMethod(int i) { return "Unknown method requested!"; } 

Ich habe die Überprüfung noch nicht hinzugefügt die Methode mit dem Attribute, um zu sehen, ob die erforderliche Signatur übereinstimmt, aber das leicht getan werden könnte, Arbeiten mit Eigenschaften auf dem Objekt MethodInfo.

Schließlich wickeln wir all dies in eine Klasse, von der jede andere Klasse erben kann. Die Unterklasse implementiert dann Methoden, die der Signatur folgen, und dekoriert sie mit dem Attribut. Um die gewünschte Methode namentlich aufzurufen, rufen Sie einfach CallMethodByAccessor an.


antworten schließlich auf alle Vorschläge eine andere Methode zu verwenden, respektiere ich alle Ihre Ideen und, wenn der Anwendungsfall unterschiedlich waren, dies absolut übertrieben und unnötig wäre. Der spezifische Anwendungsfall umfasst einen Webdienst, den Benutzer und Entwickler konfigurieren können. Mit dem Webdienst kann ein Benutzer einen oder mehrere "Endpunkte" angeben. Der Endpunkt ist in diesem Fall lediglich eine Zeichenfolge. Die Bibliothek, die ich schreibe, erhält Daten vom Web-Service, und einer der Parameter ist die gewünschte "Endpunkt" -String. Die Idee ist, dass der Benutzer Endpunkte als Methoden schreiben, ihnen einen beliebigen Funktionsnamen geben und diese dann über das Attribut dem tatsächlichen Endpunkttext zuordnen kann. Zum Beispiel:

[Endpoint("user.login")] 
public string performLogin(string credentialString) 
{ 
    // ... 
} 

Da der Endpunkt Name user.login ist, können wir nicht einfach eine Klassenmethode mit diesem Namen nennen. Darüber hinaus müssten wir, selbst wenn wir dies tun würden, noch Reflektion verwenden, um in die Klasse zu graben, die korrekte Methode zu extrahieren und ein Objekt Func zu generieren, um die Methode aufzurufen. In diesem Fall vereinfacht die Verwendung von Attributen die Entwicklung einfach, da die einzige Alternative darin besteht, einen Switch/Case-Block zu verwenden, der streng eingehalten werden muss. Ich denke, es wäre viel einfacher für einen Entwickler zu "vergessen", einen weiteren case Block hinzuzufügen oder zu vergessen, einen case Block zu entfernen, der nicht mehr benötigt wird, als es wäre, die Attribute zu vermasseln. Das Attribut verknüpft den Endpunktnamen mit der Methode, anstatt den Methodennamen und den Endpunktnamen in einem völlig anderen Codeblock nachzuverfolgen.

Schließlich ist die "Standard" -Methode in diesem Fall absolut nützlich. Wenn ein Benutzer den Webdienst anweist, einen Endpunkt anzurufen, der nicht existiert, kann die App eine vernünftige Antwort, z. "Der Endpunkt 'MyEendpoint' existiert nicht." Dies wäre viel einfacher zu debuggen als einfach den API-Absturz zu sehen!

0

Ich mag geben nicht „tun dies stattdessen“ Antworten, aber ich kann das nicht innerhalb eines Kommentars setzen.

Statt dessen, zusammen mit all den Stütz Code die Attribute

public void Test() 
{ 
    Console.WriteLine(CallMethodByAccessor("test",3)); 
    Console.WriteLine(CallMethodByAccessor("lorem",int.MaxValue)); 
    Console.WriteLine(CallMethodByAccessor("I-do-not-exist",0)); 
} 

Dieser Code funktioniert das genau die gleiche Sache zu handhaben:

public void Test() 
{ 
    var methods = new MyMethods(); 
    Console.WriteLine(methods.GetTestString("test", 3)); 
    Console.WriteLine(methods.GetSomeLoremIpsumText("lorem", int.MaxValue)); 
} 

Die Sprache absichtlich so gestaltet ist, dass, wenn Eine Methode existiert nicht, Sie können sie nicht aufrufen. Das hilft uns, Fehler zur Laufzeit zu vermeiden. Wir möchten nicht, dass unsere Anwendung etwas ausführt oder ignoriert, je nachdem, ob wir einen Methodennamen richtig geschrieben haben oder nicht. Jemand könnte sich stundenlang die Haare ausziehen, um herauszufinden, warum die Anwendung nicht funktioniert, und dann erkennen, dass es daran liegt, dass sie "Lorem" falsch geschrieben haben. Methoden einfach "normal" aufzurufen, verhindert das alles. Wenn Sie einen Methodennamen falsch schreiben, zeigt Visual Studio einen Fehler an, der Code wird nicht kompiliert und es ist super einfach zu finden und zu beheben. (Plus Intellisense/Autocomplete wird uns sogar dabei helfen, die verfügbaren Methoden zu sehen.)

Wenn Sie mit der rechten Maustaste auf eine Methode klicken, führt Visual Studio Sie direkt zu dieser Methode. Mit den String-Attributen muss jemand viel mehr Code folgen, nur um herauszufinden, welche Methode tatsächlich aufgerufen wird. Und wenn sie debuggen und durchgehen müssen, müssen sie all diese zusätzlichen Dinge durchgehen.

Es ist sehr häufig eine Reihe von Klassen zu haben, die jeweils die gleiche Schnittstelle implementieren und jede Methode haben. Es ist ein gutes Design, weniger Methoden - sogar eine - in einer Schnittstelle zu haben. (Siehe Interface Segregation Principle.) Niemand wird das als "hässlich" betrachten. Aber wenn wir unsere eigene Art und Weise entwickeln, können wir andere Entwickler, die an demselben Code arbeiten, wirklich verwirren, und es kann schwierig sein, sie sogar für die Person zu halten, die sie geschrieben hat.

+0

Die Lektion, die ich vielleicht lernen sollte, ist, über den Anwendungsfall genauer zu sein. In dieser speziellen Situation empfängt meine Bibliothek Anforderungen von einem Webdienst. Der Webdienst ermöglicht einem anderen Entwickler, "Endpunkte" anzugeben. Der Entwickler könnte einen oder fünfhundert Endpunkte angeben. Ich möchte, dass der Entwickler in C# in der Lage ist, diese Endpunkte zu implementieren und einfach durch das Attribut angibt, welche Methode zu welchem ​​Endpunkt gehört. Ganz ähnlich wie die Web API selbst die Verwendung von '[Route ("/my/end/point ")]' ermöglicht. Das heißt, ich denke, ich habe eine experimentelle Lösung und wenn es funktioniert, werde ich meine eigene Antwort veröffentlichen. – fdmillion

Verwandte Themen