Obtener el nombre de la propiedad como una cadena

204

(Vea a continuación la solución que creé usando la respuesta que acepté)

Estoy tratando de mejorar la capacidad de mantenimiento de algunos códigos que implican reflexión. La aplicación tiene una interfaz .NET Remoting que expone (entre otras cosas) un método llamado Ejecutar para acceder a partes de la aplicación que no están incluidas en su interfaz remota publicada.

Así es como la aplicación designa propiedades (una estática en este ejemplo) a las que se debe acceder mediante Ejecutar:

RemoteMgr.ExposeProperty("SomeSecret", typeof(SomeClass), "SomeProperty");

Entonces un usuario remoto podría llamar:

string response = remoteObject.Execute("SomeSecret");

y la aplicación usaría la reflexión para encontrar SomeClass.SomeProperty y devolver su valor como una cadena.

Desafortunadamente, si alguien cambia el nombre de SomeProperty y olvida cambiar el tercer parámetro de ExposeProperty (), se rompe este mecanismo.

Necesito el equivalente de:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

usar como el tercer parm en ExposeProperty para que las herramientas de refactorización se encarguen de los cambios de nombre.

¿Hay alguna forma de hacer esto? Gracias por adelantado.

De acuerdo, esto es lo que terminé creando (según la respuesta que seleccioné y la pregunta a la que hizo referencia):

// <summary>
// Get the name of a static or instance property from a property access lambda.
// </summary>
// <typeparam name="T">Type of the property</typeparam>
// <param name="propertyLambda">lambda expression of the form: '() => Class.Property' or '() => object.Property'</param>
// <returns>The name of the property</returns>
public string GetPropertyName<T>(Expression<Func<T>> propertyLambda)
{
    var me = propertyLambda.Body as MemberExpression;

    if (me == null)
    {
        throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
    }

    return me.Member.Name;
 }

Uso:

// Static Property
string name = GetPropertyName(() => SomeClass.SomeProperty);

// Instance Property
string name = GetPropertyName(() => someObject.SomeProperty);

Ahora con esta genial capacidad, es hora de simplificar el método ExposeProperty. Pulir los pomos de las puertas es un trabajo peligroso ...

Gracias a todos.

Jim C
fuente
9
Es realmente apreciado que hayas agregado tu solución y atado las cosas.
Simplemente G.
Debe agregar su solución como respuesta: es mucho más conciso que la respuesta que aceptó.
Kenny Evitt
1
@Kenny Evitt: Hecho:)
Jim C
@JimC ¡Votado! Y vinculado en un comentario sobre la respuesta actualmente aceptada . ¡Gracias!
Kenny Evitt

Respuestas:

61

Usando GetMemberInfo desde aquí: Recuperando el nombre de la propiedad de la expresión lambda puede hacer algo como esto:

RemoteMgr.ExposeProperty(() => SomeClass.SomeProperty)

public class SomeClass
{
    public static string SomeProperty
    {
        get { return "Foo"; }
    }
}

public class RemoteMgr
{
    public static void ExposeProperty<T>(Expression<Func<T>> property)
    {
        var expression = GetMemberInfo(property);
        string path = string.Concat(expression.Member.DeclaringType.FullName,
            ".", expression.Member.Name);
        // Do ExposeProperty work here...
    }
}

public class Program
{
    public static void Main()
    {
        RemoteMgr.ExposeProperty("SomeSecret", () => SomeClass.SomeProperty);
    }
}
Daniel Renshaw
fuente
Eso es totalmente genial. Parece que también funcionaría en cualquier tipo de propiedad.
Jim C
Acabo de probarlo con propiedades tanto de instancia como estáticas. Hasta aquí todo bien.
Jim C
¿Alguna idea de dónde puedo obtener el ensamblaje o el paquete NuGet que contiene GetMemberInfo? No puedo encontrar nada con el paquete de 'utilidades comunes' para Microsoft Enterprise Library, que es lo que MSDN parece indicar que contiene ese método. Hay un paquete "no oficial", pero ser no oficial no es inspirador. La respuesta de JimC , que se basa en esta, es mucho más concisa y no se basa en una biblioteca aparentemente no disponible.
Kenny Evitt
1
@KennyEvitt, el método al que hace referencia es uno escrito por el autor de la pregunta que ha vinculado. Alternativa a ese método, puede usar este tipo. GetMembers msdn.microsoft.com/en-us/library/…
Bon
464

Con C # 6.0, esto ya no es un problema, como puede hacer:

nameof(SomeProperty)

Esta expresión se resuelve en tiempo de compilación para "SomeProperty" .

Documentación de MSDN de nameof .

James Ko
fuente
18
Esto es rudo y muy útil para las llamadas ModelState.AddModelError.
Michael Silver
9
Y esto es un const string! Increíble
Jack
44
@RaidenCore seguro, si está escribiendo para un microcontrolador, debe usar un lenguaje de bajo nivel como C, y si necesita exprimir cada bit de rendimiento, como el procesamiento de imágenes y videos, debe usar C o C ++. pero para el otro 95% de las aplicaciones, un marco de código administrado será lo suficientemente rápido. Finalmente, C # también se compila en código máquina, e incluso puede precompilarlo en nativo si lo desea.
Tsahi Asher
2
Por cierto, @RaidenCore, las aplicaciones que mencionaste son anteriores a C #, por eso están escritas en C ++. Si se escribieron hoy, quién sabe qué idioma se utilizó. Ver, por ejemplo, Paint.NET.
Tsahi Asher
1
¡Esto es realmente útil cuando quieres RaisePropertyen WPF! Utilice RaisePropertyChanged (nameof (propiedad)) en lugar de RaisePropertyChanged ("propiedad")
Pierre
17

Hay un truco conocido para extraerlo de la expresión lambda (esto es de la clase PropertyObserver, de Josh Smith, en su fundación MVVM):

    private static string GetPropertyName<TPropertySource>
        (Expression<Func<TPropertySource, object>> expression)
    {
        var lambda = expression as LambdaExpression;
        MemberExpression memberExpression;
        if (lambda.Body is UnaryExpression)
        {
            var unaryExpression = lambda.Body as UnaryExpression;
            memberExpression = unaryExpression.Operand as MemberExpression;
        }
        else
        {
            memberExpression = lambda.Body as MemberExpression;
        }

        Debug.Assert(memberExpression != null, 
           "Please provide a lambda expression like 'n => n.PropertyName'");

        if (memberExpression != null)
        {
            var propertyInfo = memberExpression.Member as PropertyInfo;

            return propertyInfo.Name;
        }

        return null;
    }

Lo sentimos, le faltaba algo de contexto. Esto era parte de una clase más grande donde TPropertySourceestá la clase que contiene la propiedad. Puede hacer que la función sea genérica en TPropertySource para extraerla de la clase. Recomiendo echar un vistazo al código completo de la Fundación MVVM .

Dan Bryant
fuente
Con un ejemplo de cómo llamar a la función, este es ciertamente un +1. Vaya, no vi que haya una en la afirmación de depuración, es por eso que hacer que un desarrollador se desplace horizontalmente para llegar a la parte importante de una línea es malo;)
OregonGhost
Hmmm ... necesito diseccionar este para entenderlo.
Jim C
Visual Studio 2008 marca "TPropertySource" como error ("no se puede encontrar").
Jim C
Me acabo de dar cuenta de que es un nombre de tipo, no solo un símbolo <T> como en C ++. ¿Qué representa TPropertySource?
Jim C
2
Para hacer esta compilación, simplemente puede cambiar la firma del método para leer y public static string GetPropertyName<TPropertySource>(Expression<Func<TPropertySource, object>> expression)luego llamar así:var name = GetPropertyName<TestClass>(x => x.Foo);
dav_i
16

De acuerdo, esto es lo que terminé creando (según la respuesta que seleccioné y la pregunta a la que hizo referencia):

// <summary>
// Get the name of a static or instance property from a property access lambda.
// </summary>
// <typeparam name="T">Type of the property</typeparam>
// <param name="propertyLambda">lambda expression of the form: '() => Class.Property' or '() => object.Property'</param>
// <returns>The name of the property</returns>

public string GetPropertyName<T>(Expression<Func<T>> propertyLambda)
{
    var me = propertyLambda.Body as MemberExpression;

    if (me == null)
    {
        throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
    }

    return me.Member.Name;
 }

Uso:

// Static Property
string name = GetPropertyName(() => SomeClass.SomeProperty);

// Instance Property
string name = GetPropertyName(() => someObject.SomeProperty);
Jim C
fuente
8

La clase PropertyInfo debería ayudarlo a lograr esto, si lo entiendo correctamente.

  1. Método Type.GetProperties ()

    PropertyInfo[] propInfos = typeof(ReflectedType).GetProperties();
    propInfos.ToList().ForEach(p => 
        Console.WriteLine(string.Format("Property name: {0}", p.Name));

¿Esto es lo que necesitas?

Will Marcouiller
fuente
No, aunque uso GetProperties cuando la aplicación recibe la solicitud de "SomeSecret". La aplicación busca "SomeSecret" en un mapa para descubrir que necesita encontrar una propiedad llamada "SomeProperty" en una clase llamada "SomeClass".
Jim C
nameof (SomeProperty) en realidad facilita esto desde .net 4.0 en adelante. No hay necesidad de hacks tan largos.
Div Tiwari
6

Puede usar Reflection para obtener los nombres reales de las propiedades.

http://www.csharp-examples.net/reflection-property-names/

Si necesita una forma de asignar un "Nombre de cadena" a una propiedad, ¿por qué no escribe un atributo sobre el que pueda reflexionar para obtener el nombre de la cadena?

[StringName("MyStringName")]
private string MyProperty
{
    get { ... }
}
Robert Harvey
fuente
1
Sí, así es como la aplicación maneja las solicitudes entrantes de "SomeSecret", pero no me da una herramienta para el problema ExposeProperty.
Jim C
Interesante ... entonces podría cambiar el nombre de MyProperty al contenido de su corazón siempre que no se meta con MyStringName, y si por alguna razón desea cambiarlo, debe modificar el parámetro ExposeProperty. Al menos podría agregar un comentario al lado del atributo con tal advertencia, ya que debe mirarlo para cambiar el valor del atributo (a diferencia de cambiar el nombre de una propiedad, que se puede hacer desde cualquier ubicación de referencia).
Jim C
6

Modifiqué tu solución para encadenar múltiples propiedades:

public static string GetPropertyName<T>(Expression<Func<T>> propertyLambda)
{
    MemberExpression me = propertyLambda.Body as MemberExpression;
    if (me == null)
    {
        throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
    }

    string result = string.Empty;
    do
    {
        result = me.Member.Name + "." + result;
        me = me.Expression as MemberExpression;
    } while (me != null);

    result = result.Remove(result.Length - 1); // remove the trailing "."
    return result;
}

Uso:

string name = GetPropertyName(() => someObject.SomeProperty.SomeOtherProperty);
// returns "SomeProperty.SomeOtherProperty"
hiperhumano
fuente
4

Basado en la respuesta que ya está en la pregunta y en este artículo: https://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/ I Estoy presentando mi solución a este problema:

public static class PropertyNameHelper
{
    /// <summary>
    /// A static method to get the Propertyname String of a Property
    /// It eliminates the need for "Magic Strings" and assures type safety when renaming properties.
    /// See: http://stackoverflow.com/questions/2820660/get-name-of-property-as-a-string
    /// </summary>
    /// <example>
    /// // Static Property
    /// string name = PropertyNameHelper.GetPropertyName(() => SomeClass.SomeProperty);
    /// // Instance Property
    /// string name = PropertyNameHelper.GetPropertyName(() => someObject.SomeProperty);
    /// </example>
    /// <typeparam name="T"></typeparam>
    /// <param name="propertyLambda"></param>
    /// <returns></returns>
    public static string GetPropertyName<T>(Expression<Func<T>> propertyLambda)
    {
        var me = propertyLambda.Body as MemberExpression;

        if (me == null)
        {
            throw new ArgumentException("You must pass a lambda of the form: '() => Class.Property' or '() => object.Property'");
        }

        return me.Member.Name;
    }
    /// <summary>
    /// Another way to get Instance Property names as strings.
    /// With this method you don't need to create a instance first.
    /// See the example.
    /// See: https://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/
    /// </summary>
    /// <example>
    /// string name = PropertyNameHelper((Firma f) => f.Firmenumsatz_Waehrung);
    /// </example>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="TReturn"></typeparam>
    /// <param name="expression"></param>
    /// <returns></returns>
    public static string GetPropertyName<T, TReturn>(Expression<Func<T, TReturn>> expression)
    {
        MemberExpression body = (MemberExpression)expression.Body;
        return body.Member.Name;
    }
}

Y una prueba que también muestra el uso, por ejemplo, y las propiedades estáticas:

[TestClass]
public class PropertyNameHelperTest
{
    private class TestClass
    {
        public static string StaticString { get; set; }
        public string InstanceString { get; set; }
    }

    [TestMethod]
    public void TestGetPropertyName()
    {
        Assert.AreEqual("StaticString", PropertyNameHelper.GetPropertyName(() => TestClass.StaticString));

        Assert.AreEqual("InstanceString", PropertyNameHelper.GetPropertyName((TestClass t) => t.InstanceString));
    }
}
Thomas
fuente
3

Pregunta anterior, pero otra respuesta a esta pregunta es crear una función estática en una clase auxiliar que use el CallerMemberNameAttribute.

public static string GetPropertyName([CallerMemberName] String propertyName = null) {
  return propertyName;
}

Y luego úsalo como:

public string MyProperty {
  get { Console.WriteLine("{0} was called", GetPropertyName()); return _myProperty; }
}
Jim Pedid
fuente
0

Puede usar la clase StackTrace para obtener el nombre de la función actual (o si coloca el código en una función, luego baje un nivel y obtenga la función de llamada).

Ver http://msdn.microsoft.com/en-us/library/system.diagnostics.stacktrace(VS.71).aspx

Sprotty
fuente
No sé dónde tenía en mente capturar el rastro de la pila, pero no puedo pensar en uno que contenga el nombre de la propiedad.
Jim C
Puede hacer esto, pero esto puede conducir a resultados inesperados (incluidas las excepciones) debido a las optimizaciones de compilación en línea. smelser.net/blog/post/2008/11/27/…
JoeGeeky
0

Tuve algunas dificultades para usar las soluciones ya sugeridas para mi caso de uso específico, pero finalmente lo descubrí. No creo que mi caso específico sea digno de una nueva pregunta, por lo que estoy publicando mi solución aquí como referencia. (Esto está muy relacionado con la pregunta y proporciona una solución para cualquier otra persona con un caso similar al mío).

El código con el que terminé se ve así:

public class HideableControl<T>: Control where T: class
{
    private string _propertyName;
    private PropertyInfo _propertyInfo;

    public string PropertyName
    {
        get { return _propertyName; }
        set
        {
            _propertyName = value;
            _propertyInfo = typeof(T).GetProperty(value);
        }
    }

    protected override bool GetIsVisible(IRenderContext context)
    {
        if (_propertyInfo == null)
            return false;

        var model = context.Get<T>();

        if (model == null)
            return false;

        return (bool)_propertyInfo.GetValue(model, null);
    }

    protected void SetIsVisibleProperty(Expression<Func<T, bool>> propertyLambda)
    {
        var expression = propertyLambda.Body as MemberExpression;
        if (expression == null)
            throw new ArgumentException("You must pass a lambda of the form: 'vm => vm.Property'");

        PropertyName = expression.Member.Name;
    }
}

public interface ICompanyViewModel
{
    string CompanyName { get; }
    bool IsVisible { get; }
}

public class CompanyControl: HideableControl<ICompanyViewModel>
{
    public CompanyControl()
    {
        SetIsVisibleProperty(vm => vm.IsVisible);
    }
}

La parte importante para mí es que en la CompanyControlclase el compilador solo me permitirá elegir una propiedad booleana deICompanyViewModel que facilite que otros desarrolladores lo hagan correctamente.

La principal diferencia entre mi solución y la respuesta aceptada es que mi clase es genérica y solo quiero hacer coincidir las propiedades del tipo genérico que son booleanas.

bikeman868
fuente
0

así es como lo implementé, la razón detrás es si la clase de la que desea obtener el nombre de su miembro no es estática, entonces necesita crear una instancia de eso y luego obtener el nombre del miembro. tan genérico aquí viene a ayudar

public static string GetName<TClass>(Expression<Func<TClass, object>> exp)
{
    MemberExpression body = exp.Body as MemberExpression;

    if (body == null)
    {
         UnaryExpression ubody = (UnaryExpression)exp.Body;
         body = ubody.Operand as MemberExpression;
    }

     return body.Member.Name;
}

el uso es asi

var label = ClassExtension.GetName<SomeClass>(x => x.Label); //x is refering to 'SomeClass'
Mo Hrad A
fuente