2012-04-04 10 views
5

die folgende Klasse Stellen Sie sich vor:Dynamisch Eigenschaft Getter/Setter durch Reflexion erzeugen oder ähnliche

public class Settings 
{ 
    [FileBackedProperty("foo.txt")] 
    public string Foo { get; set; } 
} 

Ich mag wäre in der Lage sein, etwas ähnlich wie oben zu schreiben und haben settings.Foo aus der Datei „foo.txt“ lesen und settings.Foo = "bar" schreiben Sie auf "foo.txt".

Offensichtlich ist dies ein vereinfachtes Beispiel, und ich würde das oben in einer Produktionsanwendung nicht tun, aber es gibt andere Beispiele, wie wenn ich Foo in ASP.net Session Zustand "foo" gespeichert werden wollte, aber ich werde müde den folgenden Code über zu schreiben und über:

public int Foo 
{ 
    get 
    { 
     if (Session["foo"] != null) 
      return Convert.ToInt32(Session["foo"]); 
     else 
      // Throw an exception or return a default value 
    } 
    set 
    { 
     Session["foo"] = value; 
    } 
} 

(Auch in diesem Beispiel wird vereinfacht, und ich würde den obigen Code nicht schreiben, ich liege tatsächlich, ich habe den obigen Code und arbeite es Refactoring , also diese Frage)

Das obige Beispiel ist in Ordnung, es sei denn, Sie haben 50 verschiedene Sitzungswerte, die alle ähnliche Logik haben. Kann ich die zweite Eigenschaft in etwas Ähnliches umwandeln? (? Mit Attributen und Reflexion, oder vielleicht eine andere Methode)

+0

Wenn Sie Visual Studio verwenden, sollten Sie sich [Code-Snippets] (http://msdn.microsoft.com/en-us/library/ms165392%28v=vs.90%29.aspx) ansehen. Sie erlauben Ihnen, etwas kurz zu tippen, auf den gewünschten Code zu erweitern und dann bestimmte Bereiche auszufüllen. Ähnlich wie bei der bestehenden propg, etc. –

+0

@JoshuaDrake Das scheint genau so schlimm wie Kopieren/Einfügen. Was ist, wenn ich die Implementierung ändern möchte? Ich muss immer noch zurückgehen und jede Immobilie einzeln ändern. – thelsdj

+0

Sie möchten also die Eigenschaften zur Laufzeit dynamisch hinzufügen? Haben Sie alle Server-Maschinen oder geringe Erwartungen an die Leistung? Nicht, dass Sie das nicht können, aber ich warne sehr davor. Wenn Sie nicht zur Laufzeit meinen, dann müssten Sie sowieso jede Codeausgabe neu generieren. –

Antwort

1

Wenn Sie so viel zu schreiben den Getter-Code zu vermeiden, eine Hilfsmethode schreiben:

public int Foo 
{ 
    get 
    { 
     return GetHelper<int>("foo"); 
    } 
    set 
    { 
     Session["foo"] = value; 
    } 
} 

public T GetHelper<T>(string name, T defaultValue = default(T)) 
{ 
    if (Session[name] != null) 
     return (T)Session[name]; 
    else 
    { 
     return defaultValue; 
    } 
} 

Wenn Sie Zugriff auf Dynamik haben, können Sie dann verwenden können, ein dynamisches Objekt, die Sitzung zu wickeln:

internal class DynamicSession : DynamicObject 
{ 
    private HttpSessionState_session; 

    public DynamicSession() 
    { 
     _session = HttpContext.Current.Session; 
    } 

    public override bool TryGetMember(GetMemberBinder binder, out object result) 
    { 
     if (_session[binder.Name] != null) 
     { 
      result = _session[binder.Name]; 
      return true; 
     } 
     result = null; 
     return false; 
    } 

    public override bool TrySetMember(SetMemberBinder binder, object value) 
    { 
     _session[binder.Name] = value; 
     return true; 
    } 
} 

Und Sie können es dann mit etwa so:

dynamic session = new DynamicSession(); 
    //These properties are "magically" put in and taken out of session! 
//get 
int foo = session.Foo; 
//set 
session.Foo = 3; 

Eine letzte Option ist etwas wie Live Templates in Resharper, um den Code viel einfacher zu schreiben.

+0

hm .. aber es * scheint * etwas OP versucht zu vermeiden eigentlich ... – Tigran

+0

@Tigran Ja, aber vielleicht ist es einfach nicht möglich. Das obige ist kürzer und hat weniger Doppelarbeit. Es scheint schlecht, dass es keinen guten Weg gibt, das zu tun, was ich will, weil es eine Menge Doppelarbeit loswerden würde. – thelsdj

+0

@thelsdj: siehe meine Antwort, könnte eine gute Option für Sie, imo. – Tigran

1

Was Sie versuchen zu tun ist "Aspect-Oriented Programming" und es ist relativ häufig für bestimmte Aufgaben, wo der notwendige Code sonst oft mit nur geringfügigen Änderungen dupliziert werden würde. Ihr Beispiel ist sicherlich qualifiziert.

Die Grundidee ist wie folgt; Sie erstellen ein Attribut, mit dem Sie Klassen oder Klassenmitglieder dekorieren können. Das Attribut definiert einen "Kontext" für ein Nachrichtenübergabesystem in der CLR, mit dem Sie einen Methodentrenner an die Methode binden können, die beim Aufruf ausgeführt wird.

Verstehen, dass es einen signifikanten Leistungseinbruch gibt; Das Objekt mit Elementen, die mit Attributen versehen sind, muss von MarshallByRefObject oder ContextBoundObject erben. Bei einer dieser Optionen tritt während der Laufzeit ein 10-maliger Treffer auf die Leistung des Objekts auf, selbst wenn Sie keine Attributverzierung durchführen.

Hier einige Beispiel-Code: http://www.developerfusion.com/article/5307/aspect-oriented-programming-using-net/3/

Sie können auch dynamische Proxies verwenden, um Objekte „on the fly“ zu schaffen, basierend auf Attribut Dekoration oder andere Reflexion basierte Informationen. Dies ist die Technologie hinter einer Menge von Sachen, die C# -Entwickler für selbstverständlich halten, wie ORMs, IoC-Frameworks, etc.Sie würden grundsätzlich so etwas wie Castle DynamicProxy verwenden, um ein Objekt zu erstellen, das wie Ihr Stammobjekt aussieht, aber Definitionen der Eigenschaften überschrieben hat, die mit den Attributen versehen sind, die die dateibasierte Populations/Persistenz-Logik aufwiesen.

3

Eine andere Option könnte für Sie die Verwendung von PostSharp sein. Sie definieren Attribute und es injiziert einen IL in endgültigen Code, so wird es nicht Ihren Quellcode ändern. Was hat seine Nachteile und seine Güter.

Dieses Produkt ist nicht kostenlos.

Einige Getting started Tipps.

Hoffe, das hilft.

+0

Sie erwähnen es injiziert "IL", ist das zur Kompilierzeit oder Anwendungsstartzeit oder etwas? Ich nehme an, dass PostSharp dann die Performance-Probleme, die AOP mit ContextBoundObject hat, umgehen kann? – thelsdj

+0

es ist eine Kompilierzeit. Natürlich gibt es keine Güte ohne Probleme :) Das wichtigste, ich würde manion, imo, ist, dass das letzte Binary, das läuft, nicht das ist, was Sie erwarten, indem Sie auf * Ihren * Code schauen. Natürlich gibt es auch einige Performance-Probleme, aber das muss an Ihrer konkreten Implementierung gemessen werden. – Tigran

+0

Ich meine, das ist Industrieklasse-Software, also lohnt sich eine Aufmerksamkeit. – Tigran

4

Ich weiß, das ist nicht das, was Sie (und auch ich) gebraucht haben; aber das ist am nächsten, ohne eine Bibliothek von Dritten zu verwenden. Sie können die Logik für get & setzen Methoden ändern und einige Caching für GetProperty und GetCustomAttributes Methoden hinzufügen oder wenn Sie bereits eine Basisklasse haben, können Sie schreiben & setzen Methoden als statische in einer Hilfsklasse. Wieder nicht die perfekte Antwort und auch eine schlechte Leistung zumindest haben kann, aber es verringert den Code kopieren und einfügen (:

HINWEIS: Es ist wichtig, um zu verhindern, dass Compiler Eigenschaften virtuellen zu machen inlining

public class SampleClass : SessionObject 
{ 
    [Session(Key = "SS_PROP")] 
    public virtual int SampleProperty 
    { 
     get { return get(); } 
     set { set(value); } 
    } 

    [Session(Key = "SS_PROP2")] 
    public virtual string SampleProperty2 
    { 
     get { return get(); } 
     set { set(value); } 
    } 
} 

[AttributeUsage(AttributeTargets.Property)] 
public class SessionAttribute : Attribute 
{ 
    public string Key { get; set; } 
} 

public abstract class SessionObject 
{ 
    Dictionary<string, object> Session = new Dictionary<string, object>(); 

    protected void set(object value) 
    { 
     StackFrame caller = new StackFrame(1); 
     MethodBase method = caller.GetMethod(); 
     string propName = method.Name.Substring(4); 
     Type type = method.ReflectedType; 
     PropertyInfo pi = type.GetProperty(propName); 
     object[] attributes = pi.GetCustomAttributes(typeof(SessionAttribute), true); 
     if (attributes != null && attributes.Length == 1) 
     { 
      SessionAttribute ssAttr = attributes[0] as SessionAttribute; 
      Session[ssAttr.Key] = value; 
     } 
    } 

    protected dynamic get() 
    { 
     StackFrame caller = new StackFrame(1); 
     MethodBase method = caller.GetMethod(); 
     string propName = method.Name.Substring(4); 
     Type type = method.ReflectedType; 
     PropertyInfo pi = type.GetProperty(propName); 
     object[] attributes = pi.GetCustomAttributes(typeof(SessionAttribute), true); 
     if (attributes != null && attributes.Length == 1) 
     { 
      SessionAttribute ssAttr = attributes[0] as SessionAttribute; 
      if (Session.ContainsKey(ssAttr.Key)) 
      { 
       return Session[ssAttr.Key]; 
      } 
     } 
     return default(dynamic); 
    } 
} 
.
0

Sie können auch die DynamicProxy nuget package from Castle.Core nutzen dieses Verhalten zu erreichen.

Sie können Anrufe abfangen die virtuellen Eigenschaften Ihrer Klasse zu erhalten und Set-Methoden für alle. Doch alle Eigenschaft Getter und Setter Sie virtuell sein ändern wollen, müssen

Ich habe eine umfassendere Antwort hier zur Verfügung gestellt: https://stackoverflow.com/a/48764825/5103354 und ein Kern ist verfügbar here.

Das folgende Verhalten zu beachten:

[Fact] 
    public void SuccessFullyRegisterGetAndSetEvents() 
    { 
     ProxyGenerator generator = new ProxyGenerator(); 
     var tracked = generator.CreateClassProxy<TrackedClass>(new GetSetInterceptor()); 
     tracked.SomeContent = "some content"; 
     Assert.Single(tracked.GetEvents()); 
     var eventAfterSomeContentAssigned = tracked.GetEvents().Last(); 
     Assert.Equal(EventType.Set, eventAfterSomeContentAssigned.EventType); 
     Assert.Equal("some content", eventAfterSomeContentAssigned.Value); 
     Assert.Equal("SomeContent", eventAfterSomeContentAssigned.PropertyInfo.Name); 
     tracked.SomeInt = 1; 
     Assert.Equal(2, tracked.GetEvents().Count); 
     var eventAfterSomeIntAssigned = tracked.GetEvents().Last(); 
     Assert.Equal(EventType.Set, eventAfterSomeContentAssigned.EventType); 
     Assert.Equal(1, eventAfterSomeIntAssigned.Value); 
     Assert.Equal("SomeInt", eventAfterSomeIntAssigned.PropertyInfo.Name); 
     var x = tracked.SomeInt; 
     Assert.Equal(3, tracked.GetEvents().Count); 
     var eventAfterSomeIntAccessed = tracked.GetEvents().Last(); 
     Assert.Equal(EventType.Get, eventAfterSomeIntAccessed.EventType); 
     Assert.Equal(1, eventAfterSomeIntAccessed.Value); 
     Assert.Equal("SomeInt", eventAfterSomeIntAccessed.PropertyInfo.Name); 
    } 

Hoffnung, das hilft.

Verwandte Themen