Ottieni il nome della proprietà come stringa


204

(Vedi sotto la soluzione che ho creato usando la risposta che ho accettato)

Sto cercando di migliorare la manutenibilità di alcuni codici che coinvolgono la riflessione. L'app ha un'interfaccia .NET Remoting che espone (tra le altre cose) un metodo chiamato Execute per accedere a parti dell'app non incluse nell'interfaccia remota pubblicata.

Ecco come l'app designa le proprietà (statiche in questo esempio) che devono essere accessibili tramite Execute:

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

Quindi un utente remoto può chiamare:

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

e l'app userebbe la reflection per trovare SomeClass.SomeProperty e restituire il suo valore come stringa.

Sfortunatamente, se qualcuno rinomina SomeProperty e si dimentica di cambiare il terzo parametro di ExposeProperty (), si rompe questo meccanismo.

Ho bisogno dell'equivalente di:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

da utilizzare come terzo parm in ExposeProperty in modo che gli strumenti di refactoring si occupassero della ridenominazione.

C'è un modo per fare questo? Grazie in anticipo.

Ok, ecco cosa ho finito per creare (in base alla risposta che ho selezionato e alla domanda a cui faceva riferimento):

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

Ora con questa fantastica funzionalità, è tempo di semplificare il metodo ExposeProperty. Lucidare le maniglie è un lavoro pericoloso ...

Grazie a tutti.


9
È davvero apprezzabile che tu abbia aggiunto la tua soluzione e legato le cose.
Semplicemente G.


È necessario aggiungere la soluzione come risposta: è molto più concisa della risposta accettata.
Kenny Evitt,

1
@Kenny Evitt: Fatto:)
Jim C

@JimC Upvoted! E collegato in un commento sulla risposta attualmente accettata . Grazie!
Kenny Evitt,

Risposte:


61

Usando GetMemberInfo da qui: Recuperando il nome della proprietà dall'espressione lambda puoi fare qualcosa del genere:

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

È assolutamente fantastico. Sembra che funzionerebbe anche su qualsiasi tipo di proprietà.
Jim C,

L'ho appena provato con entrambe le istanze e le proprietà statiche. Fin qui tutto bene.
Jim C,

Qualche idea su dove posso ottenere l'assembly o il pacchetto NuGet che contiene GetMemberInfo? Non riesco a trovare nulla con il pacchetto "utilità comuni" per Microsoft Enterprise Library, che è ciò che MSDN sembra indicare contenga quel metodo. C'è un pacchetto "non ufficiale", ma non ufficiale non è interessante. La risposta di JimC , che si basa su questo, è molto più concisa e non si basa su una libreria apparentemente non disponibile.
Kenny Evitt,

1
@KennyEvitt, il metodo a cui fa riferimento è quello scritto dall'autore della domanda che ha collegato. Alternativa a quella methodyou possibile utilizzare questo Type.GetMembers msdn.microsoft.com/en-us/library/...
Bon

464

Con C # 6.0, questo non è un problema, come puoi fare:

nameof(SomeProperty)

Questa espressione viene risolta in fase di compilazione in "SomeProperty".

Documentazione MSDN del nome di .


18
Questo è tosto e molto utile per le chiamate ModelState.AddModelError.
Michael Silver,

9
E questo è un const string! Incredibile
Jack,

4
@RaidenCore certo, se stai scrivendo per un microcontrollore dovresti usare un linguaggio di basso livello come C, e se devi spremere ogni bit di prestazioni come l'elaborazione di immagini e video, dovresti usare C o C ++. ma per l'altro 95% delle applicazioni, un framework di codice gestito sarà abbastanza veloce. Alla fine C # viene anche compilato in codice macchina, e puoi anche pre-compilarlo in nativo se vuoi.
Tsahi Asher,

2
A proposito, @RaidenCore, le app che hai citato precedono C #, ecco perché sono scritte in C ++. Se fossero stati scritti oggi, chissà quale lingua veniva usata. Vedi ad esempio Paint.NET.
Tsahi Asher,

1
Questo è davvero utile quando vuoi RaisePropertyin WPF! Usa RaisePropertyChanged (nameof (proprietà)) invece di RaisePropertyChanged ("proprietà")
Pierre

17

C'è un noto hack per estrarlo dall'espressione lambda (questo è della classe PropertyObserver, di Josh Smith, nella sua fondazione 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;
    }

Spiacente, mancava un po 'di contesto. Questo faceva parte di una classe più ampia in cui si TPropertySourcetrova la classe che contiene la proprietà. È possibile rendere generica la funzione in TPropertySource per estrarla dalla classe. Consiglio di dare un'occhiata al codice completo della MVVM Foundation .


Con un esempio di come chiamare la funzione, questo è certamente un +1. Oops, non ho visto che ce n'è uno nell'affermazione di debug - ecco perché fare uno sviluppatore scorrere orizzontalmente per arrivare alla parte importante di una linea è male;)
OregonGhost

Hmmm ... Devo dissezionare questo per capirlo.
Jim C,

Visual Studio 2008 contrassegna "TPropertySource" come errore ("impossibile trovare").
Jim C,

Ho appena realizzato che è un nome tipo non solo un simbolo <T> come in C ++. Cosa rappresenta TPropertySource?
Jim C,

2
Per fare questa compilazione puoi semplicemente cambiare la firma del metodo da leggere, public static string GetPropertyName<TPropertySource>(Expression<Func<TPropertySource, object>> expression)quindi chiamare così:var name = GetPropertyName<TestClass>(x => x.Foo);
dav_i

16

Ok, ecco cosa ho finito per creare (in base alla risposta che ho selezionato e alla domanda a cui faceva riferimento):

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

8

La classe PropertyInfo dovrebbe aiutarti a raggiungere questo obiettivo, se ho capito bene.

  1. Metodo Type.GetProperties ()

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

è di questo che hai bisogno?


No, anche se utilizzo GetProperties quando l'app riceve la richiesta di "SomeSecret". L'app cerca "SomeSecret" in una mappa per scoprire che deve trovare una proprietà chiamata "SomeProperty" in una classe chiamata "SomeClass".
Jim C,

nameof (SomeProperty) in realtà lo semplifica da .net 4.0 in poi. Non c'è bisogno di hack così lunghi.
Div Tiwari,

6

È possibile utilizzare Reflection per ottenere i nomi effettivi delle proprietà.

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

Se hai bisogno di un modo per assegnare un "Nome stringa" a una proprietà, perché non scrivi un attributo su cui riflettere per ottenere il nome della stringa?

[StringName("MyStringName")]
private string MyProperty
{
    get { ... }
}

1
Sì, è così che l'app gestisce le richieste in arrivo per "SomeSecret", ma non mi fornisce uno strumento per il problema ExposeProperty.
Jim C,

Interessante ... quindi potresti rinominare MyProperty nel contenuto del tuo cuore purché non si scherzi con MyStringName e se per qualche motivo vuoi cambiarlo, devi modificare il parametro ExposeProperty. Almeno potrei aggiungere un commento accanto all'attributo con un tale avvertimento poiché devi guardarlo per cambiare il valore dell'attributo (diversamente dalla ridenominazione di una proprietà, che può essere fatta da qualsiasi posizione di riferimento).
Jim C,

6

Ho modificato la tua soluzione per concatenare più proprietà:

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"

4

Basato sulla risposta che è già nella domanda e su questo articolo: https://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/ I sto presentando la mia soluzione a questo 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 un test che mostra anche l'utilizzo per esempio e le proprietà statiche:

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

3

Vecchia domanda, ma un'altra risposta a questa domanda è quella di creare una funzione statica in una classe di supporto che utilizza CallerMemberNameAttribute.

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

E poi usalo come:

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

0

È possibile utilizzare la classe StackTrace per ottenere il nome della funzione corrente (o se si inserisce il codice in una funzione, si abbassa un livello e si ottiene la funzione chiamante).

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


Non so dove avevi in ​​mente di catturare la traccia dello stack, ma non riesco a pensare a uno che contenga il nome della proprietà.
Jim C,

Puoi farlo, ma questo può portare a risultati imprevisti (comprese le eccezioni) a causa delle ottimizzazioni incorporate del compilatore. smelser.net/blog/post/2008/11/27/…
JoeGeeky,


0

Ho avuto qualche difficoltà a utilizzare le soluzioni già suggerite per il mio caso d'uso specifico, ma alla fine l'ho capito. Non credo che il mio caso specifico sia degno di una nuova domanda, quindi sto pubblicando la mia soluzione qui come riferimento. (Questo è strettamente correlato alla domanda e fornisce una soluzione per chiunque abbia un caso simile al mio).

Il codice con cui ho finito è simile al seguente:

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 per me è che nella CompanyControlclasse il compilatore mi permetterà solo di scegliere una proprietà booleana ICompanyViewModelche rende più facile per gli altri sviluppatori farlo bene.

La differenza principale tra la mia soluzione e la risposta accettata è che la mia classe è generica e voglio solo abbinare le proprietà del tipo generico che sono booleane.


0

è il modo in cui l'ho implementato, il motivo dietro è se la classe che si desidera ottenere il nome dal suo membro non è statica, quindi è necessario creare un istante di quello e quindi ottenere il nome del membro. così generico qui viene in aiuto

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'uso è così

var label = ClassExtension.GetName<SomeClass>(x => x.Label); //x is refering to 'SomeClass'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.