2010-11-03 12 views
5

Tengo un ListView en modo virtual, y los datos subyacentes se almacenan en un List<MyRowObject>. Cada columna del ListView corresponde a una propiedad de cadena pública de MyRowObject. Las columnas de mi ListView son configurables durante el tiempo de ejecución, de modo que cualquiera de ellas se puede deshabilitar y se pueden reordenar. Para devolver una ListViewItem para el evento RetrieveVirtualItem, tengo un método similar a:¿Existe alguna manera de evitar el uso de la reflexión para poblar mi ListView virtual?

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

El controlador de eventos para RetrieveVirtualItem una apariencia similar a:

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

Tal como era de esperar, la evaluación comparativa muestra que este método es significativamente más lento que una implementación que accede a las propiedades directamente en un orden codificado, y la desaceleración es lo suficientemente significativa como para que me gustaría encontrar una mejor solución.

La idea más prometedora que he tenido es utilizar un delegado anónimo para indicar a la clase MyRowObject cómo acceder directamente a las propiedades, pero si es posible no pude obtener la semántica correcta (dado el nombre de una propiedad almacenada en una cadena, ¿hay alguna manera de escribir un cierre para acceder directamente a esa propiedad?).

Entonces, ¿hay alguna manera de evitar el uso de la reflexión para llenar mi ListView sin perder ninguna funcionalidad?

La extensión de código abierto de ListView está fuera de límite debido a la política de la compañía.

+0

¿qué pasa con switch/case? –

+0

Expresiones (especialmente en .net 4) podría ser una manera fácil de implementar un delegado rápido haciendo lo que desee. – CodesInChaos

Respuesta

3

usted podría utilizar estas 2 funciones

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

llamar a la BuildItemGetters (lo siento por el nombre, no podía pensar en nada;) una vez con una lista de propiedades que se desean obtener de las filas. Luego simplemente llame a GetItems para cada fila. Donde obj es la fila y la lista es la que obtuviste de la otra función.

Para T sólo tiene que utilizar el nombre de clase de la fila, como:

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

por supuesto, solamente llame a la acumulación cuando las columnas cambian

0

Eche un vistazo a Reflection.Emit. Con esto, puede generar código sobre la marcha que acceda a una propiedad específica. Este artículo de CodeProject tiene una descripción interesante del mecanismo: http://www.codeproject.com/KB/cs/fast_dynamic_properties.aspx.

No he revisado a fondo el código del proyecto, pero mi primera impresión es que la idea básica parece prometedora. Sin embargo, una de las mejoras que haré es que algunas de las piezas de la clase deben ser estáticas y compartidas, por ejemplo, InitTypes y el ensamblaje dinámico creado. Por lo demás, parece que se ajusta a lo que estás buscando.

1

BindingSource y PropertyDescriptor son las técnicas más elegantes para la realización manual de Datos- vinculante, que es más o menos lo que está haciendo con el ListView cuando está en VirtualMode. Aunque generalmente utiliza la reflexión interna de todos modos, puede confiar en que funcione de manera eficiente y sin problemas.

escribí un artículo en el blog recientemente, lo que explica en detalle cómo utilizar estos mecanismos (aunque está en un contexto diferente, cuyos principios son los mismos) - http://www.brad-smith.info/blog/archives/104

+0

Gracias por su publicación, pero probé esto y el rendimiento permaneció igual. – jtabak

0

No sé lo suficiente sobre C# para decirle si eso es posible, pero voy a ir a hackear mi manera algo como esto:

  • vez, voy a tratar de conseguir 'punteros' delegado para cada miembro que necesito, y haré todo lo que a través de la reflexión - si fuera C++, esos punteros serían desviaciones vtable para la función getter de propiedades
  • creará un mapa con string-> pointer offset
  • usará el mapa para llamar a la función getter directamente a través del puntero.

Sí, parece una magia, pero supongo que alguien con suficiente conocimiento de CLR/MSIL puede arrojar luz sobre eso si es remotamente posible.

+0

¿Por qué responder si realmente no sabes cómo hacerlo? Todos pueden adivinar ... – jgauffin

0

Aquí hay otra variante de almacenamiento en caché de los métodos get para cada propiedad.

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

para obtener un valor de propiedad:

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

Creo que estaba tratando de evitar invocaciones debido a problemas de velocidad – Doggett

+0

Bueno, usar los delegados debería ser más rápido que invocar el método de propiedades GetValue. – jgauffin

Cuestiones relacionadas