Obter o nome da propriedade como uma sequência

203

(Veja abaixo a solução que criei usando a resposta que aceitei)

Estou tentando melhorar a manutenção de algum código que envolve reflexão. O aplicativo possui uma interface .NET Remoting que expõe (entre outras coisas) um método chamado Execute para acessar partes do aplicativo não incluídas em sua interface remota publicada.

Aqui está como o aplicativo designa propriedades (estáticas neste exemplo) que devem ser acessíveis via Execute:

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

Portanto, um usuário remoto pode ligar para:

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

e o aplicativo usaria a reflexão para encontrar SomeClass.SomeProperty e retornar seu valor como uma sequência.

Infelizmente, se alguém renomeia SomeProperty e esquece de alterar o terceiro par de ExposeProperty (), ele quebra esse mecanismo.

Eu preciso do equivalente a:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

para usar como o terceiro parm em ExposeProperty, para que as ferramentas de refatoração cuidem de renomear.

Existe uma maneira de fazer isso? Desde já, obrigado.

Ok, eis o que acabei criando (com base na resposta que selecionei e na pergunta que ele referenciou):

// <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);

Agora, com esse recurso interessante, é hora de simplificar o método ExposeProperty. Polir maçanetas é um trabalho perigoso ...

Obrigado a todos.

Jim C
fonte
9
É muito bom saber que você adicionou sua solução e amarrou as coisas.
Simplesmente G.
Você deve adicionar sua solução como resposta - é muito mais concisa do que a resposta que você aceitou.
precisa saber é o seguinte
1
@Kenny Evitt: Feito:)
Jim C
@JimC Upvoted! E vinculado em um comentário sobre a resposta atualmente aceita . Obrigado!
Kenny Evitt

Respostas:

61

Usando GetMemberInfo a partir daqui: Recuperando o nome da propriedade da expressão lambda, você pode fazer algo assim:

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
fonte
Isso é totalmente legal. Parece que também funcionaria em qualquer tipo de propriedade.
Jim C
Eu apenas tentei com propriedades de instância e estáticas. Por enquanto, tudo bem.
Jim C
Alguma idéia de onde posso obter a montagem ou o pacote NuGet que contém GetMemberInfo? Não consigo encontrar nada com o pacote 'utilitários comuns' da Microsoft Enterprise Library, que é o que o MSDN parece indicar contém esse método. Existe um pacote "não oficial", mas não oficial não é inspirador. A resposta de JimC , baseada nesta, é muito mais concisa e não depende de uma biblioteca aparentemente indisponível.
Kenny Evitt
1
@KennyEvitt, o método que ele está referenciando é o escrito pelo autor da pergunta que ele vinculou. Alternativa a esse método, você pode usar esse Tipo. Type.GetMembers msdn.microsoft.com/en-us/library/…
Bon
464

Com o C # 6.0, isso agora não é um problema, como você pode fazer:

nameof(SomeProperty)

Essa expressão é resolvida no momento da compilação para "SomeProperty".

Documentação do MSDN de nameof .

James Ko
fonte
18
Isso é foda e muito útil para chamadas ModelState.AddModelError.
Michael Silver
9
E este é um const string! Incrível
Jack
4
@RaidenCore Com certeza, se você está escrevendo para um microcontrolador, deve usar uma linguagem de baixo nível como C, e se precisar diminuir todo o desempenho, como processamento de imagem e vídeo, deve usar C ou C ++. mas para os outros 95% dos aplicativos, uma estrutura de código gerenciado será rápida o suficiente. Eventualmente, o C # também é compilado no código da máquina e você pode pré-compilá-lo para o nativo, se desejar.
Tsahi Asher
2
A propósito, @RaidenCore, os aplicativos que você mencionou são anteriores ao C #, por isso são escritos em C ++. Se eles foram escritos hoje, quem sabe que idioma foi usado. Veja, por exemplo, Paint.NET.
Tsahi Asher
1
Isso é realmente útil quando você quer RaisePropertyno WPF! Use RaisePropertyChanged (nameof (propriedade)) em vez de RaisePropertyChanged ( "propriedade")
Pierre
17

Existe um hack conhecido para extraí-lo da expressão lambda (isto é da classe PropertyObserver, de Josh Smith, em sua fundação 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;
    }

Desculpe, faltou algum contexto. Isso fazia parte de uma classe maior, onde TPropertySourceé a classe que contém a propriedade. Você pode tornar a função genérica em TPropertySource para extraí-la da classe. Eu recomendo dar uma olhada no código completo da MVVM Foundation .

Dan Bryant
fonte
Com um exemplo de como chamar a função, este é certamente um +1. Opa, não ver que há um na declaração de depuração - é por isso fazendo um desenvolvedor horizontal de rolagem para chegar até a parte importante de uma linha é mau;)
OregonGhost
Hmmm ... eu preciso dissecar este para entender.
Jim C
O Visual Studio 2008 sinaliza "TPropertySource" como erro ("não pode ser encontrado").
Jim C
Acabei de perceber que é um nome de tipo e não apenas um símbolo <T> como em C ++. O que TPropertySource representa?
Jim C
2
Para fazer esta compilação você pode simplesmente alterar a assinatura do método para ler public static string GetPropertyName<TPropertySource>(Expression<Func<TPropertySource, object>> expression)em seguida, chamar assim:var name = GetPropertyName<TestClass>(x => x.Foo);
dav_i
16

Ok, eis o que acabei criando (com base na resposta que selecionei e na pergunta que ele referenciou):

// <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
fonte
8

A classe PropertyInfo deve ajudá-lo a conseguir isso, se bem entendi.

  1. Método Type.GetProperties ()

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

É isto que você precisa?

Will Marcouiller
fonte
Não, embora eu use GetProperties quando o aplicativo recebe a solicitação de "SomeSecret". O aplicativo consulta "SomeSecret" em um mapa para descobrir que ele precisa encontrar uma propriedade chamada "SomeProperty" em uma classe chamada "SomeClass".
Jim C
nameof (SomeProperty) na verdade facilita isso a partir do .net 4.0 em diante. Não há necessidade de hacks tão longos.
Div Tiwari
6

Você pode usar o Reflection para obter os nomes reais das propriedades.

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

Se você precisa de uma maneira de atribuir um "Nome da string" a uma propriedade, por que não escreve um atributo que possa refletir para obter o nome da string?

[StringName("MyStringName")]
private string MyProperty
{
    get { ... }
}
Robert Harvey
fonte
1
Sim, é assim que o aplicativo lida com solicitações de entrada para "SomeSecret", mas não me fornece uma ferramenta para o problema ExposeProperty.
Jim C
Interessante ... então você pode renomear MyProperty para o conteúdo do seu coração, desde que não mexa com MyStringName e, por algum motivo, queira alterá-lo, precisará modificar o parm ExposeProperty. Pelo menos eu poderia adicionar um comentário ao lado do atributo com esse aviso, já que você precisa observá-lo para alterar o valor do atributo (diferente da renomeação de uma propriedade, que pode ser feita a partir de qualquer local de referência).
Jim C
6

Modifiquei sua solução para encadear várias propriedades:

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"
hypehuman
fonte
4

Com base na resposta que já está na pergunta e neste artigo: https://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/ I estou apresentando minha solução para esse 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;
    }
}

E um teste que também mostra o uso de propriedades estáticas e de instância:

[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
fonte
3

Pergunta antiga, mas outra resposta a essa pergunta é criar uma função estática em uma classe auxiliar que usa o CallerMemberNameAttribute.

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

E então use-o como:

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

Você pode usar a classe StackTrace para obter o nome da função atual (ou, se você colocar o código em uma função, desça um nível e obtenha a função de chamada).

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

Sprotty
fonte
Não sei onde você pretendia capturar o rastreamento de pilha, mas não consigo pensar em um que contenha o nome da propriedade.
Jim C
Você pode fazer isso, mas isso pode levar a resultados inesperados (incluindo exceções) devido a otimizações embutidas do compilador. smelser.net/blog/post/2008/11/27/…
JoeGeeky
0

Tive alguma dificuldade em usar as soluções já sugeridas para meu caso de uso específico, mas descobri isso eventualmente. Não acho que meu caso específico seja digno de uma nova pergunta; portanto, estou postando minha solução aqui para referência. (Isso está intimamente relacionado à questão e fornece uma solução para qualquer pessoa com um caso semelhante ao meu).

O código que eu acabei com fica assim:

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);
    }
}

A parte importante para mim é que, na CompanyControlclasse, o compilador permitirá apenas escolher uma propriedade booleana deICompanyViewModel que torna mais fácil para outros desenvolvedores acertar.

A principal diferença entre minha solução e a resposta aceita é que minha classe é genérica e só quero corresponder propriedades do tipo genérico que são booleanos.

bikeman868
fonte
0

é como eu o implementei, a razão por trás disso é que, se a classe que você deseja obter o nome do membro não é estática, é necessário criar uma instância disso e obter o nome do membro. tão genérico aqui vem para ajudar

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;
}

o uso é assim

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