2010-07-29 9 views
6

Quiero construir un método que acepte un param de cadena, y un objeto del cual me gustaría devolver un miembro en particular basado en el param. Por lo tanto, el método más fácil es construir una sentencia switch:declaración de interruptor indexado, o equivalente? .net, C#

public GetMemberByName(MyObject myobj, string name) 
{ 
    switch(name){ 
    case "PropOne": return myobj.prop1; 
    case "PropTwo": return myobj.prop2; 
    } 
} 

Esto funciona bien, pero puede terminar con una lista bastante grande ... así que estaba ansioso por ver si hay una manera, sin necesidad de escribir un montón de estructuras if-else anidadas, para lograr esto de forma indexada, de modo que el campo coincidente se encuentre por índice en lugar de caer a través de un interruptor hasta que se encuentre una coincidencia.

Consideré usar un Dictionary<string, something> para dar acceso rápido a las cadenas correspondientes (como el miembro clave) pero como deseo acceder a un miembro de un objeto pasado, no estoy seguro de cómo se podría lograr esto .

  • Estoy tratando específicamente de evitar la reflexión, etc. para tener una implementación muy rápida. Probablemente usaré generación de código, por lo que la solución no necesita ser pequeña/estricta, etc.

  • Originalmente estaba construyendo un diccionario de pero cada objeto lo estaba inicializando. Así que comencé a mover esto a un único método que puede buscar los valores basados ​​en las teclas: una declaración de cambio. Pero como ya no estoy indexado, me temo que las búsquedas continuas que invocan este método serían lentas.

  • SO: Estoy buscando una manera de combinar el rendimiento de una búsqueda indexada/hash (como usa el diccionario) con el retorno de propiedades particulares de un objeto pasado. Probablemente pondré esto en un método estático dentro de cada clase para la que se usa.

+0

Dado que myobj parece ser muy específico (a diferencia de un simple 'objeto'), ¿no es myobj.Property enough en lugar de GetMemberByName (myobj," Property ")? – Humberto

+0

Solo un pensamiento, interpreté mal tu intención basándome en el título. El ejemplo lo aclaró para mí. Parece que la pregunta se parece más a "¿Cómo puedo acceder a una propiedad de un objeto por nombre", ¿verdad? –

+0

¿Por qué 'name' es una cadena? ¿Esto es algo que ingresa un usuario? ¿Es de un archivo? ¿Hay alguna razón por la cual no es un 'enum'? –

Respuesta

7

Aquí es una manera fácil se puede utilizar un diccionario:

Dictionary<string, Func<MyObject, object>> propertyNameAssociations; 

    private void BuildPropertyNameAssociations() 
    { 
     propertyNameAssociations = new Dictionary<string, Func<MyObject, object>>(); 
     propertyNameAssociations.Add("PropOne", x => x.prop1); 
     propertyNameAssociations.Add("PropTwo", x => x.prop2); 
    } 

    public object GetMemberByName(MyObject myobj, string name) 
    { 
     if (propertyNameAssociations.Contains(name)) 
      return propertyNameAssociations[name](myobj); 
     else 
      return null; 
    } 
+0

No está mal, incluso podría usar el reflejo para generar el código. La única advertencia es la falta de uso genérico. – ChaosPandion

+0

@ ChaosPandion - Sí, espero que el tipo de devolución sea algo específico, pero lamentablemente se omitió de la pregunta. Tal vez el OP puede reemplazar "objeto" con algo mejor. –

+0

Jeffrey, justo lo que estaba buscando, gracias señor. –

2

Puede usar reflection para obtener una propiedad dinámicamente en tiempo de ejecución. Aquí hay un fragmento de una pequeña utilidad de reflexión que escribí. Esto se escribe como un método de extensión que permitiría fácilmente que usted pueda obtener una propiedad de la instancia de la clase

myInstance.GetProperty<string>("Title"); // Replace 'string' with the proper return value. 

El código:

public static class ReflectionExtensions 
{ 
    private const BindingFlags DefaultFlags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public; 

    public static T GetProperty<T>(this object instance, string propertyName) 
    { 
     PropertyInfo property = GetPropertyInfo(instance, propertyName); 
     if (property == null) 
     { 
      var message = string.Format("The Type, '{0}' does not implement a '{1}' property", instance.GetType().AssemblyQualifiedName, propertyName); 
      throw new NotImplementedException(message); 
     } 

     return (T)property.GetValue(instance, null); 
    } 

    private static PropertyInfo GetPropertyInfo(object instance, string propertyName) 
    { 
     Type type = instance.GetType(); 
     return type.GetProperty(propertyName, DefaultFlags); 
    } 
} 
+0

+1, excelente sugerencia para utilizar un método de extensión genérico. –

+0

Sin embargo, una desventaja (ver mi comentario en la publicación de Justin Niessner) es que si el tipo de devolución es genérico, debes especificarlo en la llamada al método, porque el compilador no puede deducir el tipo de T usando solo el valor de retorno. Así que su llamada al método sería en realidad MyInstance.GetProperty ("Título"), o cualquier otro tipo de propiedad. –

+0

@John M Gant, tiene razón, he actualizado mi muestra. –

0

Bueno, suponiendo que el nombre coincida con el nombre real de la propiedad (a diferencia de su ejemplo), esto probablemente se manejaría mejor a través de la reflexión.

0

No puedes hacerlo con un índice, pero podrías usar el reflejo.

8

Aquí hay una maqueta rápida de algo que podría funcionar para cualquier clase (utilizando la reflexión en lugar de una sentencia switch):

public static object GetMemberByName<T>(T obj, string name) 
{ 
    PropertyInfo prop = typeof(T).GetProperty(name); 
    if(prop != null) 
     return prop.GetValue(obj, null); 

    throw new ArgumentException("Named property doesn't exist."); 
} 

o una versión Método de extensión (que todavía funciona en cualquier tipo de objeto):

public static object GetMemberByName<T>(this T obj, string name) 
{ 
    PropertyInfo prop = typeof(T).GetProperty(name); 
    if(prop != null) 
     return prop.GetValue(obj, null); 

    throw new ArgumentException("Named property doesn't exist."); 
} 

Obviamente, hay algunas comprobaciones de errores adicionales que podría querer hacer, pero esto sería un comienzo.

También devolví el objeto tipo de los métodos por un motivo. Esto permite que la persona que llama maneje el lanzamiento del valor como lo considere apropiado (si es que necesitan lanzarlo).

+0

+1 Iba a responder de la misma manera, así que le di el punto. –

+0

Curiosamente, la versión de Wallace Breza acepta un objeto y devuelve una T, mientras que esta versión acepta una T y devuelve un objeto. También podría tener un método que sea genérico en cuanto a entrada y salida. –

+0

@John M Gant: Elegí hacerlo genérico en el objeto de devolución para que no haya ningún molde involucrado al usar el valor de retorno. También quería que el método de extensión apareciera en intellisence para todos los objetos. –

0

Es posible que desee intentar utilizar algo como esto.

private static readonly Dictionary<Type, Dictionary<string, PropertyInfo>> _cache = new Dictionary<Type,Dictionary<string,PropertyInfo>>(); 
public static T GetProperty<T>(object obj, string name) 
{ 
    if (obj == null) 
    { 
     throw new ArgumentNullException("obj"); 
    } 
    else if (name == null) 
    { 
     throw new ArgumentNullException("name"); 
    } 

    lock (_cache) 
    { 
     var type = obj.GetType(); 
     var props = default(Dictionary<string, PropertyInfo>); 
     if (!_cache.TryGetValue(type, out props)) 
     { 
      props = new Dictionary<string, PropertyInfo>(); 
      _cache.Add(type, props); 
     } 
     var prop = default(PropertyInfo); 
     if (!props.TryGetValue(name, out prop)) 
     { 
      prop = type.GetProperty(name); 
      if (prop == null) 
      { 
       throw new MissingMemberException(name); 
      } 
      props.Add(name, prop); 
     } 
     return (T)prop.GetValue(obj, null); 
    } 
} 
5

Hay algunas opciones que usted puede intentar.

Opción 1: haga que el objeto almacene los valores de propiedad dinámicamente.

public GetMemberByName(MyObject myobj, string name) 
{ 
    return myobj.GetProperty(name); 
} 

public class MyObject 
{ 
    private Dictionary<string, object> m_Properties = new Dictionary<string, object>(); 

    public object GetProperty(string name) 
    { 
     return m_Properties[name]; 
    } 

    public void SetProperty(string name, object value) 
    { 
     m_Properties[name] = value; 
    } 

    public object Prop1 
    { 
     get { return GetProperty("PropOne"); } 
     set { SetProperty("PropOne", value); } 
    } 

    public object Prop2 
    { 
     get { return GetProperty("PropTwo"); } 
     set { SetProperty("PropTwo", value); } 
    } 
} 

Opción 2: Uso de reflexión.

public GetMemberByName(MyObject myobj, string name) 
{ 
    return typeof(MyObject).GetProperty(name).GetValue(obj, null); 
} 

Opción 3: Dejarlo como está.

Esta es una opción razonable porque las declaraciones de interruptor en los tipos de datos de cadena se convertirán a una búsqueda Dictionary una vez que las sentencias de número de casos alcancen un cierto umbral. Ese umbral es 7 en el compilador C# 3.0. Entonces, la búsqueda será O (1) sin importar cuántas declaraciones de casos haya. No escaneará a través de cada uno.

Cuestiones relacionadas