Obtenir le nom de la propriété sous forme de chaîne

204

(Voir ci-dessous la solution que j'ai créée en utilisant la réponse que j'ai acceptée)

J'essaie d'améliorer la maintenabilité de certains codes impliquant la réflexion. L'application possède une interface .NET Remoting exposant (entre autres) une méthode appelée Exécuter pour accéder à des parties de l'application non incluses dans son interface distante publiée.

Voici comment l'application désigne les propriétés (statiques dans cet exemple) qui sont censées être accessibles via Execute:

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

Ainsi, un utilisateur distant pourrait appeler:

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

et l'application utiliserait la réflexion pour trouver SomeClass.SomeProperty et retourner sa valeur sous forme de chaîne.

Malheureusement, si quelqu'un renomme SomeProperty et oublie de modifier le 3ème paramètre de ExposeProperty (), il rompt ce mécanisme.

J'ai besoin de l'équivalent de:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

à utiliser comme 3e paramètre dans ExposeProperty afin que les outils de refactoring se chargent des renommages.

Y a-t-il un moyen de faire cela? Merci d'avance.

D'accord, voici ce que j'ai fini par créer (en fonction de la réponse que j'ai sélectionnée et de la question à laquelle il a fait référence):

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

Usage:

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

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

Maintenant, avec cette fonctionnalité intéressante, il est temps de simplifier la méthode ExposeProperty. Le polissage des poignées de porte est un travail dangereux ...

Merci tout le monde.

Jim C
la source
9
C'est vraiment apprécié que vous ayez ajouté votre solution et lié les choses.
Simply G.
Vous devez ajouter votre solution comme réponse - elle est beaucoup plus concise que la réponse que vous avez acceptée.
Kenny Evitt
1
@Kenny Evitt: Fait:)
Jim C
@JimC Upvoted! Et lié dans un commentaire sur la réponse actuellement acceptée . Merci!
Kenny Evitt

Réponses:

61

En utilisant GetMemberInfo à partir d'ici: En récupérant le nom de la propriété à partir d'une expression lambda, vous pouvez faire quelque chose comme ceci:

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
la source
C'est totalement cool. Il semble que cela fonctionnerait également sur n'importe quel type de propriété.
Jim C
Je viens de l'essayer avec des propriétés d'instance et statiques. Jusqu'ici tout va bien.
Jim C
Une idée où je peux obtenir l'assembly ou le package NuGet qui contient GetMemberInfo? Je ne trouve rien avec le package «Common Utilities» pour Microsoft Enterprise Library, qui est ce que MSDN semble indiquer contient cette méthode. Il y a un paquet "non officiel" mais être non officiel n'est pas inspirant. La réponse de JimC , basée sur celle-ci, est beaucoup plus concise et ne repose pas sur une bibliothèque apparemment indisponible.
Kenny Evitt
1
@KennyEvitt, la méthode à laquelle il fait référence est celle écrite par l'auteur de la question qu'il a liée. Alternative à cette methodyou peut utiliser cette Type.GetMembers msdn.microsoft.com/en-us/library/...
Bon
464

Avec C # 6.0, ce n'est plus un problème, comme vous pouvez le faire:

nameof(SomeProperty)

Cette expression est résolue à la compilation à "SomeProperty".

Documentation MSDN de nameof .

James Ko
la source
18
C'est dur à cuire et très utile pour les appels ModelState.AddModelError.
Michael Silver
9
Et c'est un const string! Incroyable
Jack
4
@RaidenCore bien sûr, si vous écrivez pour un microcontrôleur, vous devez utiliser un langage de bas niveau comme C, et si vous avez besoin de comprimer chaque bit de performance comme le traitement d'image et de vidéo, vous devez utiliser C ou C ++. mais pour les 95% restants des applications, un framework de code managé sera assez rapide. Finalement, C # est également compilé en code machine, et vous pouvez même le précompiler en natif si vous le souhaitez.
Tsahi Asher
2
À propos, @RaidenCore, les applications que vous avez mentionnées sont antérieures à C #, c'est pourquoi elles sont écrites en C ++. S'ils ont été écrits aujourd'hui, qui sait quelle langue a été utilisée. Voir par exemple Paint.NET.
Tsahi Asher
1
C'est vraiment utile quand vous voulez RaisePropertydans WPF! Utilisez RaisePropertyChanged (nom de (propriété)) au lieu de RaisePropertyChanged ("propriété")
Pierre
17

Il existe un hack bien connu pour l'extraire de l'expression lambda (il s'agit de la classe PropertyObserver, par Josh Smith, dans sa fondation 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;
    }

Désolé, il manquait du contexte. Cela faisait partie d'une classe plus grande où se TPropertySourcetrouve la classe contenant la propriété. Vous pouvez rendre la fonction générique dans TPropertySource pour l'extraire de la classe. Je recommande de jeter un œil au code complet de la MVVM Foundation .

Dan Bryant
la source
Avec un exemple de comment appeler la fonction, il s'agit certainement d'un +1. Oups, je n'ai pas vu qu'il y en a une dans l'assertion de débogage - c'est pourquoi faire un développeur faire défiler horizontalement pour accéder à la partie importante d'une ligne est mauvais;)
OregonGhost
Hmmm ... J'ai besoin de disséquer celui-ci pour le comprendre.
Jim C
Visual Studio 2008 signale "TPropertySource" comme erreur ("introuvable").
Jim C
Je viens de réaliser que c'est un nom de type et pas seulement un symbole <T> comme en C ++. Que représente TPropertySource?
Jim C
2
Pour faire cette compilation, vous pouvez simplement changer la signature de la méthode pour lire public static string GetPropertyName<TPropertySource>(Expression<Func<TPropertySource, object>> expression)puis appeler comme ceci:var name = GetPropertyName<TestClass>(x => x.Foo);
dav_i
16

D'accord, voici ce que j'ai fini par créer (en fonction de la réponse que j'ai sélectionnée et de la question à laquelle il a fait référence):

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

Usage:

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

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

La classe PropertyInfo devrait vous aider à y parvenir, si je comprends bien.

  1. Méthode Type.GetProperties ()

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

Est-ce ce dont vous avez besoin?

Will Marcouiller
la source
Non, bien que j'utilise GetProperties lorsque l'application reçoit la demande de "SomeSecret". L'application recherche «SomeSecret» dans une carte pour découvrir qu'elle doit trouver une propriété appelée «SomeProperty» dans une classe appelée «SomeClass».
Jim C
nameof (SomeProperty) facilite cela à partir de .net 4.0. Pas besoin de longs hacks.
Div Tiwari
6

Vous pouvez utiliser Reflection pour obtenir les noms réels des propriétés.

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

Si vous avez besoin d'un moyen d'attribuer un "nom de chaîne" à une propriété, pourquoi n'écrivez-vous pas un attribut sur lequel vous pouvez réfléchir pour obtenir le nom de la chaîne?

[StringName("MyStringName")]
private string MyProperty
{
    get { ... }
}
Robert Harvey
la source
1
Oui, c'est ainsi que l'application gère les demandes entrantes pour "SomeSecret", mais elle ne me donne pas d'outil pour le problème ExposeProperty.
Jim C
Intéressant ... alors vous pouvez renommer MyProperty au contenu de votre cœur tant que vous ne jouez pas avec MyStringName, et si pour une raison quelconque vous voulez le changer, vous devez modifier le paramètre ExposeProperty. Au moins, je pourrais ajouter un commentaire à côté de l'attribut avec un tel avertissement car vous devez le regarder pour changer la valeur de l'attribut (contrairement au changement de nom d'une propriété, qui peut être fait à partir de n'importe quel emplacement de référence).
Jim C
6

J'ai modifié votre solution pour enchaîner plusieurs propriétés:

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

Usage:

string name = GetPropertyName(() => someObject.SomeProperty.SomeOtherProperty);
// returns "SomeProperty.SomeOtherProperty"
hypehumain
la source
4

Basé sur la réponse qui se trouve déjà dans la question et sur cet article: https://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/ I présente ma solution à ce problème:

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

Et un test qui montre également l'utilisation par exemple et les propriétés statiques:

[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
la source
3

Ancienne question, mais une autre réponse à cette question consiste à créer une fonction statique dans une classe d'assistance qui utilise CallerMemberNameAttribute.

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

Et puis utilisez-le comme:

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

Vous pouvez utiliser la classe StackTrace pour obtenir le nom de la fonction actuelle (ou si vous placez le code dans une fonction, descendez d'un niveau et obtenez la fonction appelante).

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

Sprotty
la source
Je ne sais pas où vous aviez en tête de capturer la trace de la pile, mais je ne peux pas penser à celle qui contiendrait le nom de la propriété.
Jim C
Vous pouvez le faire, mais cela peut conduire à des résultats inattendus (y compris des exceptions) en raison des optimisations en ligne du compilateur. smelser.net/blog/post/2008/11/27/…
JoeGeeky
0

J'ai eu quelques difficultés à utiliser les solutions déjà suggérées pour mon cas d'utilisation spécifique, mais je l'ai finalement compris. Je ne pense pas que mon cas spécifique soit digne d'une nouvelle question, donc je poste ma solution ici pour référence. (Ceci est très étroitement lié à la question et fournit une solution pour toute autre personne ayant un cas similaire au mien).

Le code avec lequel je me suis retrouvé ressemble à ceci:

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 partie importante pour moi est que dans la CompanyControlclasse, le compilateur ne me permettra que de choisir une propriété booléenne ICompanyViewModelqui facilite la tâche des autres développeurs.

La principale différence entre ma solution et la réponse acceptée est que ma classe est générique et je veux seulement faire correspondre les propriétés du type générique qui sont booléennes.

bikeman868
la source
0

C'est comme ça que je l'ai implémenté, la raison derrière est que si la classe dont vous voulez obtenir le nom de son membre n'est pas statique, vous devez créer un instantané de cela et ensuite obtenir le nom du membre. si générique vient ici pour aider

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

l'utilisation est comme ça

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