2009-04-04 14 views
33

Duplicar posible:
Finding the Variable Name passed to a Function in C#¿Cómo se obtiene el nombre de una variable como se escribió físicamente en su declaración?

La clase siguiente contiene todos los campo ciudad.

Necesito determinar dinámicamente el nombre del campo tal como está escrito en la declaración de clase , es decir, necesito obtener la cadena "ciudad" de una instancia de la ciudad del objeto.

He intentado hacer esto examinando su Tipo en DoSomething() pero no puedo encontrarlo cuando examino el contenido del Tipo en el depurador.

¿Es posible?

public class Person 
{ 
    public string city = "New York"; 

    public Person() 
    { 
    } 


    public void DoSomething() 
    { 
    Type t = city.GetType(); 

    string field_name = t.SomeUnkownFunction(); 
    //would return the string "city" if it existed! 
    } 
} 

Algunas personas en sus respuestas a continuación me han preguntado por qué quiero hacer esto. He aquí por qué.

En mi situación en el mundo real, hay un atributo personalizado por encima de la ciudad.

[MyCustomAttribute("param1", "param2", etc)] 
public string city = "New York"; 

Necesito este atributo en otro código. Para obtener el atributo, uso reflection. Y en el código reflexión tengo que escribir la cadena "ciudad"

MyCustomAttribute attr; 
Type t = typeof(Person); 

foreach (FieldInfo field in t.GetFields()) 
{ 

    if (field.Name == "city") 
    { 
    //do stuff when we find the field that has the attribute we need 
    } 

} 

Ahora bien, esto no es un tipo seguro. Si cambiara la variable "ciudad" a "workCity" en mi declaración de campo en persona esta línea sería un fracaso a menos que sabía para actualizar la cadena

if (field.Name == "workCity") 
//I have to make this change in another file for this to still work, yuk! 
{ 
} 

Así que estoy tratando de encontrar la manera de pasar la cadena de este código sin tipearlo físicamente

Sí, podría declararlo como una constante de cadena en Persona (o algo por el estilo) pero aún estaría tipeando dos veces.

¡Uf! ¡Eso fue difícil de explicar!

Gracias

Gracias a todos los que respondieron a esta * mucho *. Me envió a un nuevo camino para entender mejor las expresiones lambda. Y creó una nueva pregunta.

+0

Alight, He editado mi respuesta, y ahora lamentablemente tengo que ir a la cama, así que usted está por su cuenta de aquí en adelante. –

+1

En C# 6.0 y superior, puede usar la función 'nameof' para obtenerlo es decir, 'nameof (city)' devolverá 'city' –

Respuesta

7

city en este caso es un ejemplo de tipo string. Cuando llama al .GetType(), devuelve el tipo de cadena real, que tiene sin conocimiento en absoluto de su instancia de ciudad particular.

Estoy teniendo dificultades para ver por qué no puede simplemente escribir "ciudad" en el código como una cadena literal aquí, si eso es lo que necesita. Tal vez sería útil si usted compartió para qué quiere usar este valor y en qué circunstancias llamará a su función DoSomething().

Por el momento, mi mejor conjetura es que lo que realmente quiere hacer es reflejar toda la clase Person para obtener una lista de los campos de esa clase:

public void DoSomething() 
{ 
    MemberInfo[] members = this.GetType().GetMembers(); 

    // now you can do whatever you want with each of the members, 
    // including checking their .Name properties. 
} 

bien, con base en su edición Tengo algo más para usted.

Puede encontrar el nombre de campos que están decoradas con su atributo en tiempo de ejecución como esto:

Type t = typeof(Person); 
foreach (MemberInfo member in t.GetMembers() 
      .Where(m => 
       m.GetCustomAttributes(typeof(MyCustomAttribute)).Any() )) 
{ 
    // "member" is a MemberInfo object for a Peson member that is 
    // decorated with your attribute 
} 

También puede utilizar banderas de unión en los primeros GetMembers() para limitarlo a sólo campos , si tu quieres.

+0

Podría hacerlo, pero no es seguro. Si alguien cambió el nombre del campo a LocalCity, por ejemplo: cadena pública LocalCity = "Nueva York"; Mi código podría fallar a menos que cada instancia de "city" haya sido reemplazada por "LocalCity" – Petras

+0

A menos que se trate de serialización o depuración/registro, realmente no debería hacerlo de esta manera. – Samuel

+1

@Petras: si alguien hiciera eso tendrías que recompilar de todos modos. Entonces, ¿cómo estás usando esta cadena? –

0
t.GetField("city", BindingFlags.Public | BindingFlags.Instance); 

o puede llamar GetFields() para obtener todos los campos

+0

t == typeof (cadena) por lo que obtener sus campos es bastante inútil. – Samuel

+0

Vaya, lo siento. Pensé que t = typeof (Persona) – Canton

0

Es necesario llamar a obtener el tipo de la clase Persona. La iteración de los campos de la clase como en la respuesta a continuación

0

Esto no es posible (creo que en realidad lo es pero invoca varios hacks y el uso de lambdas). Si desea almacenar atributos sobre un Person y poder obtener el nombre del atributo fácilmente, sugiero usar un Dictionary<TKey, TValue> del espacio de nombres System.Collections.Generic.

Y siempre puede hacer que las propiedades públicas que envuelven el diccionario.

public class Person 
{ 
    Dictionary<string, string> attributes = new Dictionary<string, string(); 
    public string City 
    { 
    get { return attributes["city"]; } 
    set { attributes["city"] = value; } 
    } 

    public Person() 
    { 
    City = "New York"; 
    } 
} 

Y puede obtener una lista de todos los atributos con attributes.Keys.

0

Tener un vistazo a este post, ya que es similar a lo que estás tratando de hacer:

Finding the variable name passed to a function

(especialmente la respuesta de Konrad Rudolph) Otro enfoque podría ser simplemente agregar "ciudad" como uno de los parámetros en el atributo y pez que salen más tarde.

4

Usted mencionó "es decir que necesito para obtener la cadena 'ciudad' de una instancia de la ciudad de objeto". ¿Está buscando obtener el nombre del campo del valor del campo? Por ejemplo: si hay 2 persona objeto uno con ciudad "Nueva York" y el otro con ciudad "Londres", ¿está buscando la función para devolver "ciudad". ¿Es esto lo que quieres decir con dinámica?


Con su diseño actual, siempre tendrá que comparar el nombre del campo de FieldInfo con una cadena. ¿Qué sucede si en su lugar desacoplo esto para que tenga el identificador para usar con fines de comparación durante la reflexión como parte del atributo. Algo como esto:

public enum ReflectionFields 
{ 
    CITY = 0, 
    STATE, 
    ZIP,  
    COUNTRY 

} 

[AttributeUsage(AttributeTargets.Field,AllowMultiple=false)] 
public class CustomFieldAttr : Attribute 
{ 
    public ReflectionFields Field { get; private set; } 
    public string MiscInfo { get; private set; } 

    public CustomFieldAttr(ReflectionFields field, string miscInfo) 
    { 
     Field = field; 
     MiscInfo = miscInfo; 
    } 
} 

public class Person 
{ 
    [CustomFieldAttr(ReflectionFields.CITY, "This is the primary city")] 
    public string _city = "New York"; 

    public Person() 
    { 
    } 
    public Person(string city) 
    { 
     _city = city; 
    } 

} 

public static class AttributeReader<T> where T:class 
{ 
    public static void Read(T t) 
    { 
     //get all fields which have the "CustomFieldAttribute applied to it" 
     var fields = t.GetType().GetFields().Where(f => f.GetCustomAttributes(typeof(CustomFieldAttr), true).Length == 1); 

     foreach (var field in fields) 
     { 
      var attr = field.GetCustomAttributes(typeof(CustomFieldAttr), true).First() as CustomFieldAttr; 
      if (attr.Field == ReflectionFields.CITY) 
      { 
       //You have the field and you know its the City,do whatever processing you need. 
       Console.WriteLine(field.Name); 
      } 
     }    
    } 
} 

public class Program 
{ 
    public static void Main(string[] args) 
    { 
     PPerson p1 = new PPerson("NewYork"); 
     PPerson p2 = new PPerson("London"); 
     AttributeReader<PPerson>.Read(p1); 
     AttributeReader<PPerson>.Read(p2); 

} 
} 

Se puede renombrar libremente campo _city de la persona a otra cosa y su código de llamada seguirá funcionando ya que el código utilizando la reflexión está tratando de identificar el campo utilizando el valor de enumeración ReflectionFields establecido como parte de la inicialización del atributo establecido en el campo.

+0

Lo tienes, si hubiera dos objetos, entonces "city" sería devuelto por – Petras

+0

Basado en su comentario, he editado mi respuesta con una posible solución que podría funcionar para usted. Espero que esto ayude –

1

Dos cosas aquí.

Número uno, como se señaló anteriormente, obtendrá el Tipo de cadena, no para Persona. Entonces typeof (Person) .GetMembers() te dará la lista de miembros.

Número dos y, lo que es más importante, parece que está malinterpretando el propósito de los atributos. En general, los atributos se usan para marcar a un miembro para un procesamiento específico o para agregar información adicional. Aquí está usando el nombre para indicar qué procesamiento desea, y el atributo para especificar parámetros, que es la mezcla de metáforas, o algo así.

La respuesta de Abhijeet es más apropiada, marque el campo como un campo de la ciudad, luego haga lo que quiera con él. En lo que no estoy de acuerdo es en que utilizaría diferentes clases de atributos, en lugar de una enumeración.

Algo así como:

public class MyAttribute : Attribute 
    { 

    } 

    [AttributeUsage(AttributeTargets.Field)] 
    public class MyCityAttribute : MyAttribute 
    { 
    } 

    [AttributeUsage(AttributeTargets.Field] 
    public class MyNameAttribute: MyAttribute 
    { 
    } 

    public class Person 
    { 

     [MyCity] 
     public string city = "New York"; 

     [MyCity] 
     public string workCity = "Chicago"; 

     [MyName] 
     public string fullName = "John Doe"; 

     public Person() 
     { 
     } 


     public void DoSomething() 
     { 
      Type t = typeof(Person); 
      FieldInfo[] fields = t.GetFields(BindingFlags.Instance | BindingFlags.Public); 

      foreach (var field in fields) 
      { 
       MyAttribute[] attributes = field.GetCustomAttributes(typeof(MyAttribute)); 
       if (attributes.Count > 0) 
       { 
        if (attributes[0] is MyCityAttribute) 
        { 
         //Dosomething for city 
         break; 
        } 

        if (attributes[0] is MyNameAttribute) 
        { 
         //Dosomething for names 
         break; 
        } 
       } 
      } 
     } 
    } 

Esto permitirá utilizar diferentes parámetros para Mycity vs MiNombre que tendría más sentido en el contexto del tratamiento de cada uno.

Creo que con su 'yuk' comentario anterior, golpeó el clavo en la cabeza. Que tendrías que cambiar una constante de cadena si cambias el nombre de tu variable es un indicador de que estás haciendo algo mal.

+0

@Darren: Eso funcionaría también, yo estaba tratando de limitar la cantidad de clases de atributos que tendrían que crearse, de ahí el enfoque enum, pero si el problema en cuestión necesitaba mantener un estado específico dentro del atributo, elegiría crear clases de atributos por separado. Solo una opción personal. –

+0

Absolutamente, no significaba que lo que sugirió era "incorrecto", simplemente no cómo lo haría. :) –

49

Quizás necesite esto. Funciona bien.

He encontrado here.

static void Main(string[] args) 
{ 
    var domain = "matrix"; 
    Check(() => domain); 
    Console.ReadLine(); 
} 

static void Check<T>(Expression<Func<T>> expr) 
{ 
    var body = ((MemberExpression)expr.Body); 
    Console.WriteLine("Name is: {0}", body.Member.Name); 
    Console.WriteLine("Value is: {0}", ((FieldInfo)body.Member) 
    .GetValue(((ConstantExpression)body.Expression).Value)); 
} 

de salida será:

 
Name is: 'domain' 
Value is: 'matrix' 
+1

Me gusta esta solución y he creado una utilidad divertida ction que es similar a esto - pero a veces el cuerpo. El miembro no es un FieldInfo - es un PropertyInfo - ¿cómo puedo obtener el valor en este caso? – William

+1

Ghetto one-liner: Console.WriteLine (((MemberExpression) ((Expresión >) ((= = ciudad)). Cuerpo) .Member.Name); –

+0

"No se ha podido lanzar el objeto del tipo 'System.Linq.Expressions.TypedConstantExpression' para escribir 'System.Linq.Expressions.MemberExpression'." :( – weberc2

4

Sí, es posible !!!

Trate de hacer esto ...

public string DoSomething(object city) 
    { 
     return city.GetType().GetProperty("Name",typeof(string)).GetValue(city,null); 
    } 
+0

La mejor manera de reflejar el nombre de la variable en la mayoría de las circunstancias –

38

Sé que esto es cuestión de edad, pero yo estaba tratando de lograr lo mismo y Google me envió aquí. Después de muchas horas finalmente encontré la manera. Espero que alguien más lo encuentre útil.

En realidad, hay más maneras de lograr esto:

static void Main(string[] args) 
{ 
    GetName(new { var1 }); 
    GetName2(() => var1); 
    GetName3(() => var1); 
} 

static string GetName<T>(T item) where T : class 
{ 
    return typeof(T).GetProperties()[0].Name; 
} 

static string GetName2<T>(Expression<Func<T>> expr) 
{ 
    return ((MemberExpression)expr.Body).Member.Name; 
} 

static string GetName3<T>(Func<T> expr) 
{ 
    return expr.Target.GetType().Module.ResolveField(BitConverter.ToInt32(expr.Method.GetMethodBody().GetILAsByteArray(), 2)).Name; 
} 

El primero es más rápido. Los últimos 2 son aproximadamente 20 veces más lentos que el primero.

http://abdullin.com/journal/2008/12/13/how-to-find-out-variable-or-parameter-name-in-c.html

+2

Muchas gracias Markos, su respuesta realmente ayudó – IneedHelp

+0

Apoyos para una solución "20 veces más rápido" – w00ngy

+0

Buenas alternativas, probé esto en una aplicación de consola. Al comenzar con la solución Debug # 1 es 5-10 veces más rápido que # 2 y 1.5-2 veces más rápido que # 3. Curiosamente, al comenzar sin depuración, el n. ° 1 y el n. ° 2 funcionan casi por igual, y el ganador es el n. ° 3 aproximadamente 2 veces más rápido que el n. ° 1 y n. ° 2. Parece que mi solución tiene un rendimiento diferente en mi sistema. Sería interesante escuchar los resultados de otros usuarios. – donttellya

Cuestiones relacionadas