2010-07-11 10 views
10

Tengo varios objetos grandes que tienen cada uno aproximadamente 60 strings. Tengo que recortar todas esas cadenas, y me gustaría hacerlo sin tener que ir a this.mystring = this.mystring.Trim(). En cambio, estoy buscando una manera de que cada objeto automáticamente descubra sus propias cadenas y luego realice la operación.Irretar a través del propio Strings & Trim de Object

Sé un poco sobre la reflexión, pero no lo suficiente, pero creo que esto es posible?

Además, no estoy seguro de si esto importa, pero algunas propiedades de cadenas son de solo lectura (solo tienen un getter), por lo que esas propiedades tendrían que omitirse.

¿Ayuda?

Respuesta

14

Bien, es bastante fácil obtener todas las propiedades, y averiguar cuáles son cadenas y escribibles. LINQ lo hace aún más fácil.

var props = instance.GetType() 
        .GetProperties(BindingFlags.Instance | BindingFlags.Public) 
        // Ignore non-string properties 
        .Where(prop => prop.PropertyType == typeof(string)) 
        // Ignore indexers 
        .Where(prop => prop.GetIndexParameters().Length == 0) 
        // Must be both readable and writable 
        .Where(prop => prop.CanWrite && prop.CanRead); 

foreach (PropertyInfo prop in props) 
{ 
    string value = (string) prop.GetValue(instance, null); 
    if (value != null) 
    { 
     value = value.Trim(); 
     prop.SetValue(instance, value, null); 
    } 
} 

Es posible que desee establecer sólo la propiedad si el recorte en realidad hace la diferencia, para evitar cálculos redundantes para las características complejas - o puede no ser un problema para usted.

Hay varias maneras de mejorar el rendimiento si es necesario - cosas como:

  • Simplemente almacenamiento en caché de las propiedades relevantes para cada tipo
  • Usando Delegate.CreateDelegate para construir los delegados de los captadores y definidores
  • Posiblemente utilizar árboles de expresión, aunque no estoy seguro de si podrían ayudar aquí

No tomaría ninguno de esos pasos a menos que fuera e es en realidad un problema.

+0

Pensé que los árboles de propiedades se almacenan en caché automáticamente si no están en modo de depuración? Lo he leído un par de veces. ¿Esto no es verdad? – Alex

+1

En este caso, 'Expression' sería un problema, incluso en 4.0; Sin embargo, la combinación de 'Acción ' en un solo delegado funcionaría bien. –

+0

@Alex - las expresiones lambda en la * fuente * pueden hacer algunas cosas inteligentes, usando un campo de delegado de respaldo. Sin embargo, AFAIK no es cierto para árboles construidos manualmente y compilados ('Compile') en tiempo de ejecución. –

3

Algo así como:

foreach (PropertyInfo prop in obj.GetType().GetProperties(
     BindingFlags.Instance | BindingFlags.Public)) 
    { 
     if (prop.CanRead && prop.CanWrite && prop.PropertyType == typeof(string) 
      && (prop.GetIndexParameters().Length == 0)) // watch for indexers! 
     { 
      var s = (string)prop.GetValue(obj, null); 
      if (!string.IsNullOrEmpty(s)) s = s.Trim(); 
      prop.SetValue(obj, s, null); 
     } 
    } 
+0

El 'Tipo sin parámetros.La llamada GetProperties() 'incluye propiedades estáticas, que * sospecho * no deberían incluirse aquí. –

+1

@Jon - buen punto, gracias. No mencionaré 'IsReadable'; p Además, es posible que desee comprobar si hay indexadores. –

+0

@Marc: arreglará :) –

0

lo que para ampliar en esto un poco, tengo un objeto complejo con Listas de Listas y quería atravesar eso y recortar toda la cadena objetos secundarios también. Estoy publicando lo que hice a partir de lo que construí de @Jon en su respuesta. Tengo curiosidad de si había una mejor manera de hacerlo o si me perdí algo obvio.

Los objetos que tengo son más complejos que este pero deberían ilustrar lo que estaba intentando.

public class Customer 
{ 
    public string Name { get; set; } 
    public List<Contact> Contacts { get; set; } 
} 

public class Contact 
{ 
    public string Name { get; set; } 
    public List<Email> EmailAddresses {get; set;} 
} 

public class Email 
{ 
    public string EmailAddress {get; set;} 
} 


    private void TrimWhitespace(object instance) 
    { 
     if (instance != null) 
     { 
      var props = instance.GetType() 
        .GetProperties(BindingFlags.Instance | BindingFlags.Public) 
       // Ignore indexers 
        .Where(prop => prop.GetIndexParameters().Length == 0) 
       // Must be both readable and writable 
        .Where(prop => prop.CanWrite && prop.CanRead); 

      foreach (PropertyInfo prop in props) 
      { 
       if (instance is IEnumerable) 
       { 
        foreach (var item in (IEnumerable)instance) 
        { 
         TrimWhitespace(item); 
        } 
       } 
       else if (prop.GetValue(instance, null) is string) 
       { 
        string value = (string)prop.GetValue(instance, null); 
        if (value != null) 
        { 
         value = value.Trim(); 
         prop.SetValue(instance, value, null); 
        } 
       } 
       else 
        TrimWhitespace(prop.GetValue(instance, null)); 
      } 
     } 
    } 

¿Pensamientos?

1

No es necesario hacer IEnumerable comprobar en el props-loop y si la instancia real es un IEnumerable, los accesorios se ignoran. Solución para IEnumerable parte:

private void TrimWhitespace(object instance) 
{ 
    if (instance != null) 
    { 
     if (instance is IEnumerable) 
     { 
      foreach (var item in (IEnumerable)instance) 
      { 
       TrimWhitespace(item); 
      } 
     } 

     var props = instance.GetType() 
       .GetProperties(BindingFlags.Instance | BindingFlags.Public) 
      // Ignore indexers 
       .Where(prop => prop.GetIndexParameters().Length == 0) 
      // Must be both readable and writable 
       .Where(prop => prop.CanWrite && prop.CanRead); 

     foreach (PropertyInfo prop in props) 
     { 
      if (prop.GetValue(instance, null) is string) 
      { 
       string value = (string)prop.GetValue(instance, null); 
       if (value != null) 
       { 
        value = value.Trim(); 
        prop.SetValue(instance, value, null); 
       } 
      } 
      else 
       TrimWhitespace(prop.GetValue(instance, null)); 
     } 
    } 
}