2012-04-04 28 views
5

Immaginate la seguente classe:generare dinamicamente proprietà getter/setter attraverso la riflessione o simili

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

Mi piacerebbe essere in grado di scrivere qualcosa di simile al precedente e hanno settings.Foo leggere dal file "foo.txt" e settings.Foo = "bar" scrivere su "foo.txt".

Ovviamente questo è un esempio semplificato e non farei quanto sopra in un'applicazione di produzione, ma ci sono altri esempi, come se volessi che Foo venisse memorizzato nello stato di ASP.net Session "foo" ma mi stanco di scrivere il seguente codice più e più volte:

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; 
    } 
} 

(ancora una volta questo esempio è semplificato e non vorrei scrivere il codice di cui sopra, in realtà sto mentendo, ho il codice di cui sopra e sto lavorando al refactoring , quindi questa domanda)

L'esempio precedente va bene a meno che non si abbiano 50 valori di sessione diversi che hanno tutti una logica simile. Quindi è in qualche modo possibile convertire la seconda proprietà in qualcosa di simile alla prima? (? Utilizzando gli attributi e la riflessione, o forse qualche altro metodo)

+0

Se si utilizza Visual Studio, è necessario dare un'occhiata a [frammenti di codice] (http://msdn.microsoft.com/en-us/library/ms165392%28v=vs.90%29.aspx). Ti consentono di digitare qualcosa di breve, di espandere il codice desiderato e quindi di compilare le aree specificate. Simile al propg esistente, ecc. –

+0

@JoshuaDrake Questo sembra tanto male quanto copiare/incollare. Cosa succede se voglio cambiare l'implementazione? Devo ancora tornare indietro e modificare ogni proprietà individualmente. – thelsdj

+0

Quindi si desidera aggiungere dinamicamente le proprietà in fase di runtime? Avete tutte le macchine server o basse aspettative di prestazioni? Non che tu non possa farlo, ma vorrei caldamente metterlo in guardia. Se non intendevi a runtime, dovresti comunque rigenerare qualsiasi output di codice. –

risposta

1

Se si vuole evitare di scrivere il codice di getter così tanto, scrivere un metodo di supporto:

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; 
    } 
} 

Se si ha accesso alle dinamiche, allora si può usare un oggetto dinamico per avvolgere la sessione:

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; 
    } 
} 

E quindi è possibile utilizzare in questo modo:

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

Un'opzione finale è qualcosa come Live Templates in Resharper per semplificare la digitazione del codice.

+0

hm .. ma * sembra * qualcosa di OP sta cercando di evitare in realtà ... – Tigran

+0

@Tigran Sì, ma forse non è proprio possibile. Quanto sopra è più corto e ha meno duplicazioni. Sembra brutto che non ci sia un buon modo per fare ciò che voglio perché eliminerebbe una tonnellata di duplicati. – thelsdj

+0

@thelsdj: vedi la mia risposta, potrebbe essere una buona opzione per te, imo. – Tigran

1

Quello che stai cercando di fare è chiamato "programmazione orientata all'aspetto" ed è relativamente comune per alcune attività in cui il codice necessario sarebbe altrimenti duplicato molte volte con solo modifiche molto minori. Il tuo esempio certamente si qualifica.

L'idea di base è la seguente; crei un attributo che puoi utilizzare per decorare classi o membri della classe. L'attributo definisce un "contesto" per un sistema che trasmette messaggi nel CLR che consente di agganciare un intercettatore di metodi al metodo che verrà eseguito quando viene chiamato.

Comprendere che è coinvolto un significativo colpo di prestazioni; l'oggetto con membri decorati da attributi deve ereditare da MarshallByRefObject o ContextBoundObject; o uno di questi subirà un colpo di 10 volte alla performance dell'oggetto durante il runtime, anche se in realtà non si esegue alcuna decorazione di attributo.

Ecco qualche esempio di codice: http://www.developerfusion.com/article/5307/aspect-oriented-programming-using-net/3/

È inoltre possibile utilizzare proxy dinamici per creare oggetti "al volo" sulla base di decorazione attributo o altre informazioni di riflessione-based. Questa è la tecnologia alla base di un sacco di cose che gli sviluppatori C# danno per scontato, come ORM, framework IoC, ecc. Ecc.Fondamentalmente useresti qualcosa come Castle DynamicProxy per creare un oggetto che assomigliasse al tuo oggetto base, ma aveva delle definizioni sovrascritte delle proprietà decorate con gli attributi che avevano la logica di popolamento/persistenza basata su file.

3

Un'altra opzione potrebbe essere per voi è l'uso di PostSharp. Si definiscono gli attributi e si inietta un codice IL nel codice finale, quindi non cambierà il codice sorgente. Che ha i suoi difetti e i suoi beni.

Questo prodotto non è gratuito.

Alcuni suggerimenti Getting started.

Spero che questo aiuti.

+0

Si dice che inietta 'IL', è in fase di compilazione o di avvio dell'applicazione o qualcosa del genere? Suppongo che PostSharp risolva i problemi di prestazioni di AOP che utilizzano ContextBoundObject? – thelsdj

+0

è un tempo di compilazione. Naturalmente non c'è bontà senza problemi :) Il principale, vorrei dire, è che il binario finale che viene eseguito non è quello che ti aspetti guardando * il tuo * codice. Ci sono, naturalmente, anche alcuni problemi di prestazioni, ma è qualcosa che deve essere misurato sulla vostra implementazione concreta. – Tigran

+0

Voglio dire che questo è un software di classe industriale, quindi merita solo un'attenzione. – Tigran

4

So che questo non è ciò che voi (e anche io) abbiamo bisogno; ma questo è il più vicino senza usare una libreria di terze parti. È possibile modificare la logica per ottenere i metodi set & e aggiungere alcuni cahcing per i metodi GetProperty e GetCustomAttributes oppure se si dispone già di una classe base che è possibile scrivere, ottenere i metodi set & come statici in una classe helper. Anche in questo caso non è la risposta perfetta e può anche avere una cattiva performance ma almeno si riduce il codice si copia e incolla (:

NOTA: E 'importante fare le proprietà virtuali al fine di evitare che il compilatore li 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

È anche possibile utilizzare il DynamicProxy nuget package from Castle.Core per raggiungere questo comportamento.

È possibile intercettare le chiamate ai metodi get e set per tutte le proprietà virtuali della tua classe. Tuttavia tutti i getter di proprietà e setter che si desidera modificare devono essere virtuale

Ho fornito una risposta più completa qui: https://stackoverflow.com/a/48764825/5103354 e un elenco è disponibile here. Va osservato

il seguente comportamento:

[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); 
    } 

Spero che questo aiuti.

Problemi correlati