2009-08-27 11 views
12

Odio tener un montón de métodos "izquierda/derecha". Cada vez que se agrega o elimina una propiedad, tengo que arreglar cada método. Y el código en sí mismo se ve ... mal.¿Cómo usar la reflexión para simplificar constructores y comparaciones?

public Foo(Foo other) 
{ 
    this.Bar = other.Bar; 
    this.Baz = other.Baz; 
    this.Lur = other.Lur; 
    this.Qux = other.Qux; 
    this.Xyzzy= other.Xyzzy; 
} 

Realmente esto es sólo un bucle desenrollado que recorre en iteración las propiedades, ellos copiando entre objetos. Entonces, ¿por qué no ser honesto sobre ese hecho? Reflexión al rescate!

public Foo(IFoo other) 
{ 
    foreach (var property in typeof(IFoo).GetProperties()) 
    { 
     property.SetValue(this, property.GetValue(other, null), null); 
    } 
} 

que puede simplemente estar tratando de forzar un paradigma que aprendí de Lua en C#, pero este ejemplo en particular, no parece demasiado mal olor a mí. A partir de aquí, empecé a hacer cosas más complejas que eran sensibles al orden de los campos. Por ejemplo, en lugar de tener una pila de if declaraciones casi idénticas a componer una cadena a partir de los campos, sólo iterar sobre ellos en el orden deseado:

public override string ToString() 
{ 
    var toJoin = new List<string>(); 
    foreach (var property in tostringFields) 
    { 
     object value = property.GetValue(this, null); 
     if (value != null) 
      toJoin.Add(value.ToString()); 
    } 
    return string.Join(" ", toJoin.ToArray()); 
} 
private static readonly PropertyInfo[] tostringFields = 
{ 
    typeof(IFoo).GetProperty("Bar"), 
    typeof(IFoo).GetProperty("Baz"), 
    typeof(IFoo).GetProperty("Lur"), 
    typeof(IFoo).GetProperty("Qux"), 
    typeof(IFoo).GetProperty("Xyzzy"), 
}; 

Así que ahora tengo el iterabilidad que quería, pero todavía tienen montones de código duplicando cada propiedad que me interesa (también estoy haciendo esto para CompareTo, usando un conjunto diferente de propiedades en un orden diferente). Peor que eso es la pérdida de tipeo fuerte. Esto realmente está empezando a oler.

¿Y qué hay del uso de atributos en cada propiedad para definir el orden? Empecé por este camino y de hecho funcionó bien, pero simplemente hizo que todo pareciera hinchado. Funciona muy bien semánticamente, pero siempre tengo cuidado con el uso de funciones avanzadas simplemente porque son "ordenadas". ¿Usar la reflexión de esta manera es excesiva? ¿Hay alguna otra solución al problema del código izquierdo/derecho que me falta?

+0

i'dd agregar una etiqueta de idioma para obtener más televidentes – Toad

+0

Tenga en cuenta, la reflexión siempre viene con un impacto en el rendimiento en C#. ¿Con qué frecuencia estarás manipulando a Foos de esta manera? Una solución híbrida podría ser usar Reflection.Emit y almacenar en caché el código resultante: inicio más lento, ejecución rápida. – zildjohn01

+1

C# 4.0 viene con un compilador incorporado, que puede ser útil para generar código y usarlo, por ahora también tenemos un problema similar, pero diseñamos una solución usando estructuras XML Class y usamos nuestro propio método de generación de código para construir clases que generar muchos otros métodos que dependen de los nombres de los miembros, es básicamente un ORML pero hace todo de forma automática. –

Respuesta

5

Usar el reflejo en sí mismo no es malo, pero tendrá un impacto en el rendimiento, especialmente si lo hace recursivamente.

No soy seguidor de los constructores de copias codificadas, ya sea porque los desarrolladores olvidan actualizarlos cuando agregan nuevas propiedades a una clase.

Existen otras maneras de lograr lo que desea, incluyendo Marc Gravells Hyper Property Descriptor o si desea aprender algunos IL y OPCodes, puede usar System.Reflection.Emit o incluso Cecil from Mono.

He aquí un ejemplo del uso de Hyper propiedad descriptor que posiblemente puede adaptar a sus necesidades:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using Hyper.ComponentModel; 
namespace Test { 
    class Person { 
     public int Id { get; set; } 
     public string Name { get; set; } 
    } 
    class Program { 
     static void Main() { 
      HyperTypeDescriptionProvider.Add(typeof(Person)); 
      var properties = new Dictionary<string, object> { { "Id", 10 }, { "Name", "Fred Flintstone" } }; 
      Person person = new Person(); 
      DynamicUpdate(person, properties); 
      Console.WriteLine("Id: {0}; Name: {1}", person.Id, person.Name); 
      Console.ReadKey(); 
     } 
     public static void DynamicUpdate<T>(T entity, Dictionary<string, object> { 
      foreach (PropertyDescriptor propertyDescriptor in TypeDescriptor.GetProperties(typeof(T))) 
       if (properties.ContainsKey(propertyDescriptor.Name)) 
        propertyDescriptor.SetValue(entity, properties[propertyDescriptor.Name]); 
     } 
    } 
} 

Si decide continuar utilizando la reflexión, se puede reducir el rendimiento alcanzado por el almacenamiento en caché sus llamadas a GetProperties (), así:

public Foo(IFoo other) { 
    foreach (var property in MyCacheProvider.GetProperties<IFoo>()) 
     property.SetValue(this, property.GetValue(other, null), null); 
} 
+3

Type.GetProperties ya hace su propio almacenamiento en caché (tiempo de la primera llamada y las llamadas siguientes). El verdadero impacto en el rendimiento de la reflexión es la propiedad. Llamada a SetValue, que puedes mitigar con la generación de MSIL, etc. –

+0

@Rob Fonseca-Ensor Gracias, no lo sabía. – grenade

+0

Hmm ... si el impacto en el rendimiento está en establecer los valores, entonces probablemente este no sea el camino a seguir. Tratar de lidiar con todas esas cosas de bajo nivel sin duda sería excesivo. Gracias por el aporte. – Cogwheel

2

en mi humilde opinión, la reflexión es una característica muy potente de C#, pero que es muy probable que resulte en un código hinchado, y que añade mucho a la curva de aprendizaje del código y reduce el mantenimiento. Es más probable que cometa errores (una vez que la refactorización básica puede generar errores) y más miedo a cambiar el nombre de cualquier propiedad (si encuentra un nombre mejor) o cosas por el estilo.

Personalmente tengo un código con un problema similar, y tenía la misma idea de agregar atributos para mantener el orden, etc. Pero mi equipo (incluyéndome a mí) pensó que era mejor perder algo de tiempo cambiando el diseño para no necesitarlo esta. Tal vez este problema esté causado por un diseño incorrecto (bueno, fue en mi caso, pero no puedo decir lo mismo sobre el tuyo).

+0

No estoy seguro de cómo sería más propenso a errores cuando se cambian las propiedades ya que todo el punto es eliminar la necesidad de referenciar los nombres de las propiedades directamente en el código. Ahora mismo estoy construyendo un modelo de dominio que eventualmente se mapeará en una base de datos, por lo que las propiedades de adición/eliminación/cambio de nombre serán prácticamente sin interrupción durante el desarrollo (especialmente porque estoy probando TDD por primera vez) – Cogwheel

+0

Tiene "typeof (IFoo) .GetProperty (" Bar ")" en su código, que hace referencia directamente a los nombres de propiedad –

+0

De ahí los dos últimos párrafos en mi pregunta;) – Cogwheel

3

sé que ya hay una respuesta a esto, pero quería señalar que hay una biblioteca que combina algunas de las estrategias de mitigación del impacto en el rendimiento que algunas personas han discutido.

La biblioteca se llama AutoMapper y se correlaciona de un objeto a otro y lo hace al crear dinámicamente un ensamblado IL sobre la marcha. Esto asegura que no sea un golpe primera vez, se obtiene un rendimiento superior y su código sería mucho más simple:

public Foo(Foo other) 
{ 
    Mapper.Map(other, this); 
} 

Esto tiende a funcionar muy bien y tiene la ventaja añadida de no ser inventado aquí, que soy una fan de.

Hice algunas pruebas de rendimiento y después del primer golpe de 20 ms (siendo bastante rápido) era lo más cercano a 0 como usted puede conseguir. Muy impresionante.

Espero que esto ayude a alguien.

+0

O puede usar emitmapper que es aún más rápido. –

+0

No creo que emismapper estuviera disponible en '09 cuando envié esto :) –

+0

Bueno ... nunca es tarde para mejorar tu respuesta :) –

2

El problema básico es que están tratando de utilizar un lenguaje de tipos estáticos como un tipo dinámico uno.

No hay realmente una necesidad de algo sofisticado. Si desea poder iterar las propiedades, puede usar un Map <> como la tienda de respaldo para todas las propiedades en su clase.

Coincidentemente así es exactamente como los parámetros de la aplicación implmements asistente de proyectos VS para usted. (Ver System.Configuration.ApplicationSettingsBase) también es muy 'lua-como'

public bool ConfirmSync { 
     get { 
      return ((bool)(this["ConfirmSync"])); 
     } 
     set { 
      this["ConfirmSync"] = value; 
     } 
    } 
+0

¡Bonito! Estrategia muy interesante. Esto le permitiría recorrer la tienda de respaldo y copiar todo en un bucle muy simple. Muy inventivo. –

+0

Gracias por la sugerencia. Probablemente termine usando esto en algún momento, pero no es el adecuado para mi tarea actual. – Cogwheel

Cuestiones relacionadas