2010-11-03 9 views
5

Ho un ListView in modalità virtuale e i dati sottostanti vengono memorizzati in un List<MyRowObject>. Ogni colonna di ListView corrisponde a una proprietà di stringa pubblica di MyRowObject. Le colonne del mio ListView sono configurabili durante il runtime, in modo tale che ognuna di esse possa essere disabilitata e possano essere riordinate. Per restituire un ListViewItem per l'evento RetrieveVirtualItem, ho un metodo simile a:C'è un modo carino per evitare l'uso di reflection per popolare il mio ListView virtuale?

class MyRowObject 
{ 
    public string[] GetItems(List<PropertyInfo> properties) 
    { 
     string[] arr = new string[properties.Count]; 
     foreach(PropertyInfo property in properties) 
     { 
      arr[i] = (string)property.GetValue(this,null); 
     } 
     return arr; 
    } 
} 

Il gestore di eventi per RetrieveVirtualItem sembra simile a:

private void listView_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e) 
{ 
    e.Item = new ListViewItem(_virtualList[e.ItemIndex].GetItems(_currentColumns)); 
} 

Forse non a caso, l'analisi comparativa dimostra che questo metodo è significativamente più lento di un'implementazione che accede alle proprietà direttamente in un ordine codificato, e il rallentamento è abbastanza significativo che mi piacerebbe trovare una soluzione migliore.

L'idea più promettente che ho avuto è di utilizzare un delegato anonimo per dire alla classe MyRowObject come accedere direttamente alle proprietà, ma se è possibile non ho potuto ottenere la semantica giusta (dato il nome di una proprietà memorizzata in una stringa, c'è un modo in cui posso scrivere una chiusura per accedere direttamente a quella proprietà?).

Quindi, c'è un buon modo per evitare l'uso di reflection per popolare il mio ListView senza perdere alcuna funzionalità?

L'estensione open source di ListView è off-off a causa dei criteri aziendali.

+0

che dire di switch/case? –

+0

Le espressioni (specialmente su .net 4) potrebbero essere un modo semplice per implementare un delegato veloce che fa ciò che vuoi. – CodesInChaos

risposta

3

Si potrebbe utilizzare queste 2 funzioni

private List<Func<T, string>> BuildItemGetters<T>(IEnumerable<PropertyInfo> properties) 
    { 
     List<Func<T, string>> getters = new List<Func<T, string>>(); 
     foreach (var prop in properties) 
     { 
      var paramExp = Expression.Parameter(typeof(T), "p"); 

      Expression propExp = Expression.Property(paramExp, prop); 
      if (prop.PropertyType != typeof(string)) 
       propExp = Expression.Call(propExp, toString); 

      var lambdaExp = Expression.Lambda<Func<T, string>>(propExp, paramExp); 

      getters.Add(lambdaExp.Compile()); 
     } 

     return getters; 
    } 

    private string[] GetItems<T>(List<Func<T, string>> properties, T obj) 
    { 
     int count = properties.Count; 
     string[] output = new string[count]; 

     for (int i = 0; i < count; i++) 
      output[i] = properties[i](obj); 

     return output; 
    } 

chiamare i BuildItemGetters (scusate per il nome, non poteva pensare a niente;) una volta con una lista di proprietà che si desidera ottenere dalle righe. Quindi chiama i GetItem per ogni riga. Dove obj è la riga e la lista è quella che hai ottenuto dall'altra funzione.

Per T basta usare il nome della classe della fila, come:

var props = BuildItemGetters<MyRowObject>(properties); 
string[] items = GetItems(props, row); 

naturalmente, solo chiamare la generazione quando le colonne cambiano

0

Dai uno sguardo allo Reflection.Emit. Con questo, è possibile generare codice al volo che accede a una proprietà specifica. Questo articolo CodeProject ha un'interessante descrizione del meccanismo: http://www.codeproject.com/KB/cs/fast_dynamic_properties.aspx.

Non ho rivisto il codice del progetto, ma la mia prima impressione è che l'idea di base sembra promettente. Tuttavia, uno dei miglioramenti apportati è che alcuni dei componenti della classe devono essere statici e condivisi, ad esempio InitTypes e l'assembly dinamico creato. Per il resto, sembra che si adatta a ciò che stai cercando.

1

BindingSource e PropertyDescriptor sono più eleganti tecniche per l'esecuzione di dati- manuale vincolante, che è più o meno quello che stai facendo con lo ListView quando è in VirtualMode. Sebbene in genere utilizzi la riflessione internamente comunque, puoi fare affidamento su di esso per lavorare in modo efficiente e senza interruzioni.

ho scritto un articolo del blog di recente che spiega in dettaglio come utilizzare questi meccanismi (anche se è in un contesto diverso, i principi sono gli stessi) - http://www.brad-smith.info/blog/archives/104

+0

Grazie per il tuo post, ma ho provato questo e le prestazioni sono rimaste le stesse. – jtabak

0

Non so abbastanza di C# per dirvi se questo è possibile, ma andrò a incidere il mio modo con qualcosa di simile:

  • una volta, cercherò di ottenere 'puntatori delegato' per ogni membro che ho bisogno, e lo farò attraverso la riflessione - se fosse C++, quei puntatori sarebbero offset compensabili per la funzione getter di proprietà
  • creerà una mappa con string-> offset puntatore
  • utilizzerà la mappa per chiamare la funzione getter direttamente tramite il puntatore.

Sì, sembra una magia, ma credo che qualcuno con sufficiente conoscenza CLR/MSIL possa far luce su questo se è possibile da remoto.

+0

Perché rispondere se davvero non sai come farlo? Tutti possono indovinare .. – jgauffin

0

Ecco un'altra variante che memorizza nella cache i metodi get per ciascuna proprietà.

public class PropertyWrapper<T> 
    { 
     private Dictionary<string, MethodBase> _getters = new Dictionary<string, MethodBase>(); 

     public PropertyWrapper() 
     { 
      foreach (var item in typeof(T).GetProperties()) 
      { 
       if (!item.CanRead) 
        continue; 

       _getters.Add(item.Name, item.GetGetMethod()); 
      } 
     } 

     public string GetValue(T instance, string name) 
     { 
      MethodBase getter; 
      if (_getters.TryGetValue(name, out getter)) 
       return getter.Invoke(instance, null).ToString(); 

      return string.Empty; 
     } 
    } 

per ottenere un valore di proprietà:

var wrapper = new PropertyWrapper<MyObject>(); //keep it as a member variable in your form 

var myObject = new MyObject{LastName = "Arne"); 
var value = wrapper.GetValue(myObject, "LastName"); 
+0

Penso che stava cercando di evitare invocazioni a causa di problemi di velocità – Doggett

+0

Bene, l'utilizzo dei delegati dovrebbe essere più veloce del richiamo del metodo GetValue delle proprietà. – jgauffin

Problemi correlati