2008-12-14 16 views

Respuesta

41

Se necesitaría algo así como:

PropertyInfo addressProperty = typeof(Customer).GetProperty("Address"); 
ProportyInfo zipCodeProperty = addressProperty.PropertyType.GetProperty("ZipCode"); 

object address = addressProperty.GetValue(customer, null); 
object zipCode = zipCodeProperty.GetValue(address, null); 

Básicamente, si usted quiere tomar una cadena "Address.ZipCode" y navegar por ella, es necesario dividirlo por "." y luego llame a GetProperty en el tipo apropiado en cada paso para obtener la propiedad en sí, luego PropertyInfo.GetValue para obtener el siguiente valor en la cadena. Algo como esto:

public static object FollowPropertyPath(object value, string path) 
{ 
    Type currentType = value.GetType(); 

    foreach (string propertyName in path.Split('.')) 
    { 
     PropertyInfo property = currentType.GetProperty(propertyName); 
     value = property.GetValue(value, null); 
     currentType = property.PropertyType; 
    } 
    return value; 
} 

llamada así:

object zipCode = FollowPropertyPath(customer, "Address.ZipCode"); 

Tenga en cuenta que esto funciona en los tipos en tiempo de compilación de las propiedades. Si quiere que se haga frente al tipo de tiempo de ejecución (por ejemplo, si customer.Address no tiene una propiedad ZipCode, pero sí el tipo devuelto por Address), cambie property.PropertyType a property.GetType().

También tenga en cuenta que esto no tiene ningún control de errores, etc :)

+0

¡Buena respuesta! ... Superlike. – Hrishi

2
typeof (Customer).GetProperty("Address").PropertyType.GetProperty("ZipCode") 
5

Las respuestas existentes están muy bien; solo una perspectiva alternativa: en muchos escenarios es deseable usar System.ComponentModel en lugar de reflexión directa, ya que esto permite escenarios de propiedades de tiempo de ejecución, es decir, cómo DataView de DataTable expone las columnas como propiedades.

Rendimiento - de forma predeterminada, esto es bastante idéntico, pero si está haciendo esto (por ejemplo, importación/exportación de datos masivos), puede obtener aumentos de rendimiento significativos utilizando este enfoque, cortesía de HyperDescriptor.

Para utilizar System.ComponentModel, el código es similar, pero sutilmente diferente:

static void Main() 
{ 
    object obj = new Customer { Address = new Address { ZipCode = "abcdef" } }; 

    object address = GetValue(obj, "Address"); 
    object zip = GetValue(address, "ZipCode"); 

    Console.WriteLine(zip); 
} 
static object GetValue(object component, string propertyName) 
{ 
    return TypeDescriptor.GetProperties(component)[propertyName].GetValue(component); 
} 

Esto luego le da el mismo tratamiento como si se hubiera usado los datos de unión para unirse a "Address.ZipCode" (pasar por alto algunos detalles como listas, etc.).

(nota que usted podría emitir postal como cadena, etc, si usted sabe que es el tipo esperado)

Para obtener el valor de una ruta profunda (incluyendo el mismo manejo que los usos de enlace de datos lista), lo haría usar algo como:

static object ResolveValue(object component, string path) { 
    foreach(string segment in path.Split('.')) { 
     if (component == null) return null; 
     if(component is IListSource) { 
      component = ((IListSource)component).GetList(); 
     } 
     if (component is IList) { 
      component = ((IList)component)[0]; 
     } 
     component = GetValue(component, segment); 
    } 
    return component; 
} 

la lista de cosas más o menos refleja el comportamiento de regular de enlace de datos (aunque omite algunas cosas como vinculantes contextos, moneda-gerentes, etc.)

6

Jon Skeet la respuesta está muy bien, tuve que extender su método un poco, sin embargo, con el fin de dar cuenta de los casos derivados dentro de la trayectoria de la propiedad:

public static class ReflectorUtil 
{ 
    public static object FollowPropertyPath(object value, string path) 
    { 
     if (value == null) throw new ArgumentNullException("value"); 
     if (path == null) throw new ArgumentNullException("path"); 

     Type currentType = value.GetType(); 

     object obj = value; 
     foreach (string propertyName in path.Split('.')) 
     { 
      if (currentType != null) 
      { 
       PropertyInfo property = null; 
       int brackStart = propertyName.IndexOf("["); 
       int brackEnd = propertyName.IndexOf("]"); 

       property = currentType.GetProperty(brackStart > 0 ? propertyName.Substring(0, brackStart) : propertyName); 
       obj = property.GetValue(obj, null); 

       if (brackStart > 0) 
       { 
        string index = propertyName.Substring(brackStart + 1, brackEnd - brackStart - 1); 
        foreach (Type iType in obj.GetType().GetInterfaces()) 
        { 
         if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof(IDictionary<,>)) 
         { 
          obj = typeof(ReflectorUtil).GetMethod("GetDictionaryElement") 
               .MakeGenericMethod(iType.GetGenericArguments()) 
               .Invoke(null, new object[] { obj, index }); 
          break; 
         } 
         if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof(IList<>)) 
         { 
          obj = typeof(ReflectorUtil).GetMethod("GetListElement") 
               .MakeGenericMethod(iType.GetGenericArguments()) 
               .Invoke(null, new object[] { obj, index }); 
          break; 
         } 
        } 
       } 

       currentType = obj != null ? obj.GetType() : null; //property.PropertyType; 
      } 
      else return null; 
     } 
     return obj; 
    } 

    public static TValue GetDictionaryElement<TKey, TValue>(IDictionary<TKey, TValue> dict, object index) 
    { 
     TKey key = (TKey)Convert.ChangeType(index, typeof(TKey), null); 
     return dict[key]; 
    } 

    public static T GetListElement<T>(IList<T> list, object index) 
    { 
     return list[Convert.ToInt32(index)]; 
    } 

} 

Usando property.PropertyType le dará el tipo de propiedad definida en la clase obj, mientras utilizando obj.GetType() obtendrá el tipo real de la instancia de la propiedad.

EDIT: @Oliver - tienes toda la razón, gracias por notar eso. Ajusté el método para permitir listas y diccionarios genéricos. Aunque no me gusta la parte de análisis, utilicé la ingeniosa idea de Marc Gravell en this thread para obtener los valores de la propiedad del indexador.

+1

según lo que esperas obtener en la ruta, fallarías en las propiedades indexadas (por ejemplo, 'somePerson.Adresses [3] .Street') – Oliver

1

adabyron,

he creado una versión de su código para cuando sólo se necesita para agarrar los tipos, y si usted no tiene una instancia de objeto real.

public static Type FollowPropertyPath<T>(string path) 
    { 
     if (path == null) throw new ArgumentNullException("path"); 

     Type currentType = typeof(T); 

     foreach (string propertyName in path.Split('.')) 
     { 
      int brackStart = propertyName.IndexOf("["); 

      var property = currentType.GetProperty(brackStart > 0 ? propertyName.Substring(0, brackStart) : propertyName); 

      if (property == null) 
       return null; 

      currentType = property.PropertyType; 

      if (brackStart > 0) 
      { 
       foreach (Type iType in currentType.GetInterfaces()) 
       { 
        if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof (IDictionary<,>)) 
        { 
         currentType = iType.GetGenericArguments()[1]; 
         break; 
        } 
        if (iType.IsGenericType && iType.GetGenericTypeDefinition() == typeof (ICollection<>)) 
        { 
         currentType = iType.GetGenericArguments()[0]; 
         break; 
        } 
       } 
      } 
     } 

     return currentType; 
    } 
Cuestiones relacionadas