2009-02-17 8 views
7

Quindi ho una classe PropertyBag che è pensata per implementare INotifyPropertyChanged. Per fare in modo che questo codice funzioni nel modo più pulito possibile e per evitare errori dell'utente, sto usando lo stack per ottenere il nome della proprietà. Vedi, se il nome della proprietà non corrisponde esattamente alla proprietà effettiva, allora avrai un fallimento e sto cercando di proteggerlo.Un modo per evitare l'ottimizzazione in linea della proprietà in C#?

Quindi, ecco un esempio di utilizzo della classe:

public class MyData : PropertyBag 
{ 
    public MyData() 
    { 
     Foo = -1; 
    } 

    public int Foo 
    { 
     get { return GetProperty<int>(); } 
     set { SetProperty(value); } 
    } 
} 

Il codice importante per la PropertyBag di base è qui:

public abstract class PropertyBag : INotifyPropertyChanged 
{ 
    protected T GetProperty<T>() 
    { 
     string propertyName = PropertyName((new StackTrace()).GetFrame(1)); 
     if (propertyName == null) 
      throw new ArgumentException("GetProperty must be called from a property"); 

     return GetValue<T>(propertyName); 
    } 

    protected void SetProperty<T>(T value) 
    { 
     string propertyName = PropertyName((new StackTrace()).GetFrame(1)); 
     if (propertyName == null) 
      throw new ArgumentException("SetProperty must be called from a property"); 

     SetValue(propertyName, value); 
    } 

    private static string PropertyName(StackFrame frame) 
    { 
     if (frame == null) return null; 
     if (!frame.GetMethod().Name.StartsWith("get_") && 
      !frame.GetMethod().Name.StartsWith("set_")) 
      return null; 

     return frame.GetMethod().Name.Substring(4); 
    } 
} 

Quindi, ora che avete visto il mio codice, non posso dirvi il problema ... In alcuni casi in build di rilascio, il setter "Foo" nel costruttore "MyData" sembra essere ottimizzato per inline come SetProperty (-1). Sfortunatamente, questa ottimizzazione in linea fallisce il mio metodo SetProperty perché non lo chiamo più da una proprietà! FALLIRE. Sembra che non possa fare affidamento su StackTrace in questo modo.

Qualcuno può A: trovare un modo migliore per farlo ma evitare ancora di passare in "Foo" a GetProperty e SetProperty?
B: trovare un modo per dire al compilatore di non ottimizzare in questo caso?

+2

Ho dimenticato di dire: non utilizzare un dizionario per la memorizzazione: poiché si tende ad avere un numero limitato di proprietà, la scansione di un elenco semplice è in realtà molto più rapida (lo spartiacque è di circa 120 chiavi). –

+0

Puoi renderli virtuali ... – BrainSlugs83

risposta

13

L'utilizzo dello stack qui è lento e non necessario; Vorrei semplicemente usare:

get { return GetProperty<int>("Foo"); } 
set { SetProperty("Foo", value); } 

(suggerimento: ho fatto un sacco di lavoro con modelli di proprietà personalizzate, so che questo funziona bene ...)

Un'altra alternativa è una chiave di oggetto (uso riferimento uguaglianza per confrontare) - un sacco di ComponentModel funziona in questo modo, così come alcune delle proprietà in WF/WPF:

static readonly object FooKey = new object(); 
... 
get { return GetProperty<int>(FooKey); } 
set { SetProperty(FooKey, value); } 

Naturalmente, si potrebbe dichiarare un tipo per le chiavi (con una proprietà Name), e usa quello:

static readonly PropertyKey FooKey = new PropertyKey("Foo"); 

ecc; tuttavia, per rispondere alla domanda: marcarlo (ma non lo fanno fare questo) con:

[MethodImpl(MethodImplOptions.NoInlining)] 

o

[MethodImpl(MethodImplOptions.NoOptimization)] 

o

[MethodImpl(MethodImplAttributes.NoOptimization 
    | MethodImplAttributes.NoInlining)] 

+0

Grazie per la risposta. L'attributo NoInlining è quello che mi stavo chiedendo ... ma sono d'accordo ... Non lo userò. Richiede agli utenti di saperlo fare, il che è peggio che usare "Foo". Risposta fantastica per molte ragioni. Grazie. –

+0

Giusto per essere chiari, stavo usando questo meccanismo per evitare problemi di refactoring in futuro. Se si utilizza uno strumento di refactoring per modificare il nome della proprietà, il codice verrà improvvisamente a mancare, poiché il nome deve essere uguale alla proprietà. Aggiornamento –

+0

: in C# 6 è possibile utilizzare il metodo 'nameof (...)' per recuperare il nome di una proprietà. Questo ti impedisce di rielaborare i problemi in futuro. –

1

Se si desidera evitare le stringhe codificate hard può usare:

protected T GetProperty<T>(MethodBase getMethod) 
{ 
    if (!getMethod.Name.StartsWith("get_") 
    { 
     throw new ArgumentException(
      "GetProperty must be called from a property"); 
    } 
    return GetValue<T>(getMethod.Name.Substring(4)); 
} 

aggiungere più sanità mentale il controllo, come si vede in forma

quindi la proprietà ottiene diventano

public int Foo 
{ 
    get { return GetProperty<int>(MethodInfo.GetCurrentMethod()); }  
} 

set cambiano nello stesso modo.

Anche GetCurrentMethod() attraversa lo stack ma lo fa tramite una chiamata (interna) non gestita che si basa su indicatori di stack, quindi funzionerà anche in modalità di rilascio.

In alternativa per una correzione rapida [MethodImpl] con MethodImplAttributes.NoOptimization) o MethodImplAttributes.NoInlining funzionerà anche se con un impatto sulle prestazioni (anche se si considera che si sta attraversando lo stack frame ogni volta che questo colpo è trascurabile).

Un'ulteriore tecnica, ottenere un certo livello di controllo del tempo di compilazione è:

public class PropertyHelper<T> 
{ 
    public PropertyInfo GetPropertyValue<TValue>(
     Expression<Func<T, TValue>> selector) 
    { 
     Expression body = selector; 
     if (body is LambdaExpression) 
     { 
      body = ((LambdaExpression)body).Body; 
     } 
     switch (body.NodeType) 
     { 
      case ExpressionType.MemberAccess: 
       return GetValue<TValue>(
        ((PropertyInfo)((MemberExpression)body).Member).Name); 
      default: 
       throw new InvalidOperationException(); 
     } 
    } 
} 

private static readonly PropertyHelper<Xxxx> propertyHelper 
    = new PropertyHelper<Xxxx>(); 

public int Foo 
{ 
    get { return propertyHelper.GetPropertyValue(x => x.Foo); }  
} 

dove XXXXX è la classe in cui la proprietà è definita. Se la natura statica causa problemi (è possibile anche eseguire il threading o renderlo un valore di istanza).

Vorrei sottolineare che queste tecniche sono davvero per interesse, non sto suggerendo che siano buone tecniche generali da usare.

+0

Mi piace. Fa in modo che il refactoring del codice non causi errori. –

+0

Non funzionerà con le proprietà setter - (iniziano con "set_") - e anche questo è praticamente ciò che l'OP stava già facendo - ma il JITer inline il tuo metodo se hai delle ottimizzazioni attivate - quindi questo completamente ignora il problema dell'OP. – BrainSlugs83

2

L'utilizzo dello stack non è una buona idea. Si sta facendo affidamento sull'implementazione interna del compilatore per legare artificialmente il proprio pacchetto di proprietà alle proprietà della lingua.

  1. con l'obbligo di aggiungere l'attributo MethodImpl rende l'uso del proprio sacchetto di proprietà non trasparente per altri sviluppatori.
  2. anche se la proprietà contiene l'attributo MethodImpl, nulla garantisce che sarà il primo fotogramma nello stack di chiamate. È possibile che l'assemblaggio sia stato strumentato o modificato per iniettare chiamate tra la proprietà effettiva e la chiamata alla borsa di proprietà. (Pensate programmazione aspetto)
  3. Nuove lingue o anche una versione futura del compilatore C# potrebbero decorare le di accesso alle proprietà in un modo diverso, allora '_get' e
  4. Costruire lo stack di chiamate è un'operazione relativamente lenta, in quanto richiede la compresso interno stack da decomprimere e il nome di ciascun tipo e metodo che si desidera ottenere usando il reflection.

Si dovrebbe davvero solo implementare le proprietà di accesso bag di prendere un parametro per identificare la proprietà - un nome stringa (come Hastable) o di un oggetto (come la borsa proprietà di dipendenza WPF)

2

Prova il nuovo attributo [CallerMemberName].

Posizionarlo su un parametro del metodo ([CallerMemberName] callerName = null) e il compilatore riscriverà tutte le chiamate al metodo per passare automaticamente il nome del chiamante (le chiamate non passano affatto il parametro).

Non elimina alcuna ottimizzazione ed è molto più veloce di lambda o riflessione o stack e funziona in modalità di rilascio.

P.S. se CallerMemberNameAttribute non esiste nella versione del framework, basta definirlo (vuoto). È una funzionalità linguistica, non una funzione di framework.Quando il compilatore vede [CallerMemberNameAttribute] su un parametro, funziona.

+0

'CallerMemeberName' è fantastico, ma tieni presente che è necessario un compilatore C# 5.0 (anche se lo definisci tu stesso [ad esempio, per scegliere come target .NET Framework 4.0], ma stai utilizzando Visual Studio 2010, ad esempio, non funziona - hai bisogno di un compilatore che * sappia * su questo attributo). – BrainSlugs83

+0

Voglio solo aggiungere la nuova parola chiave "nameof" è una soluzione ancora migliore dato che non succede nulla in fase di runtime. –

Problemi correlati