Ruft den Namen der Eigenschaft als Zeichenfolge ab

203

(Siehe unten stehende Lösung, die ich mit der von mir akzeptierten Antwort erstellt habe.)

Ich versuche, die Wartbarkeit von Code mit Reflexion zu verbessern. Die App verfügt über eine .NET Remoting-Oberfläche, die unter anderem eine Methode namens Execute für den Zugriff auf Teile der App bereitstellt, die nicht in der veröffentlichten Remote-Oberfläche enthalten sind.

So bestimmt die App Eigenschaften (in diesem Beispiel eine statische), auf die über Execute zugegriffen werden soll:

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

Ein entfernter Benutzer könnte also anrufen:

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

und die App würde Reflection verwenden, um SomeClass.SomeProperty zu finden und seinen Wert als Zeichenfolge zurückzugeben.

Wenn jemand SomeProperty umbenennt und vergisst, den 3. Parameter von ExposeProperty () zu ändern, wird dieser Mechanismus leider unterbrochen.

Ich muss das Äquivalent von:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

Als 3. Parameter in ExposeProperty zu verwenden, damit Refactoring-Tools das Umbenennen übernehmen.

Gibt es eine Möglichkeit, dies zu tun? Danke im Voraus.

Okay, hier ist, was ich letztendlich erstellt habe (basierend auf der Antwort, die ich ausgewählt habe, und der Frage, auf die er verwiesen hat):

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

Verwendung:

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

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

Mit dieser coolen Funktion ist es jetzt an der Zeit, die ExposeProperty-Methode zu vereinfachen. Das Polieren von Türklinken ist eine gefährliche Arbeit ...

Vielen Dank an alle.

Jim C.
quelle
9
Es ist wirklich zu schätzen, dass Sie Ihre Lösung hinzugefügt und die Dinge zusammengebunden haben.
Einfach G.
Sie sollten Ihre Lösung als Antwort hinzufügen - sie ist viel präziser als die Antwort, die Sie akzeptiert haben.
Kenny Evitt
1
@ Kenny Evitt: Fertig :)
Jim C
@ JimC Upvoted! Und in einem Kommentar zur aktuell akzeptierten Antwort verlinkt . Vielen Dank!
Kenny Evitt

Antworten:

61

Verwenden von GetMemberInfo von hier aus: Wenn Sie den Eigenschaftsnamen aus dem Lambda-Ausdruck abrufen , können Sie Folgendes tun:

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
quelle
Das ist total cool. Sieht so aus, als würde es auch für jeden Eigenschaftstyp funktionieren.
Jim C
Ich habe es gerade mit Instanz- und statischen Eigenschaften versucht. So weit, ist es gut.
Jim C
Irgendeine Idee, wo ich die Assembly oder das NuGet-Paket bekommen kann, das es enthält GetMemberInfo? Ich kann mit dem Paket "Common Utilities" für die Microsoft Enterprise Library nichts finden. MSDN scheint darauf hinzuweisen, dass es diese Methode enthält. Es gibt ein "inoffizielles" Paket, aber inoffiziell zu sein ist nicht inspirierend. Die Antwort von JimC , die auf dieser basiert, ist viel prägnanter und basiert nicht auf einer scheinbar nicht verfügbaren Bibliothek.
Kenny Evitt
1
@KennyEvitt, die Methode, auf die er verweist, wurde vom Autor der von ihm verlinkten Frage geschrieben. Alternativ zu dieser Methode können Sie diesen Typ verwenden. GetMembers msdn.microsoft.com/en-us/library/…
Bon
464

Mit C # 6.0 ist dies jetzt kein Problem mehr, wie Sie es tun können:

nameof(SomeProperty)

Dieser Ausdruck wird zur Kompilierungszeit in aufgelöst "SomeProperty".

MSDN-Dokumentation von nameof .

James Ko
quelle
18
Dies ist schlecht und sehr nützlich für ModelState.AddModelError-Aufrufe.
Michael Silver
9
Und das ist ein const string! Erstaunlich
Jack
4
@RaidenCore sicher, wenn Sie für einen Mikrocontroller schreiben, sollten Sie eine einfache Sprache wie C verwenden, und wenn Sie jede Leistung wie Bild- und Videoverarbeitung einschränken müssen, sollten Sie C oder C ++ verwenden. Für die anderen 95% der Anwendungen ist ein verwaltetes Code-Framework jedoch schnell genug. Schließlich wird C # auch zu Maschinencode kompiliert, und Sie können es sogar zu Native vorkompilieren, wenn Sie möchten.
Tsahi Asher
2
Übrigens, @RaidenCore, die Apps, die Sie vor C # erwähnt haben, sind deshalb in C ++ geschrieben. Wenn sie heute geschrieben wurden, wer weiß, welche Sprache verwendet wurde. Siehe zB Paint.NET.
Tsahi Asher
1
Dies ist wirklich nützlich, wenn Sie RaisePropertyin WPF wollen! Verwenden Sie RaisePropertyChanged (nameof (Eigenschaft)) anstelle von RaisePropertyChanged ("Eigenschaft")
Pierre
17

Es gibt einen bekannten Hack, um ihn aus dem Lambda-Ausdruck zu extrahieren (dies ist aus der PropertyObserver-Klasse von Josh Smith in seiner MVVM-Stiftung):

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

Entschuldigung, hier fehlte ein Kontext. Dies war Teil einer größeren Klasse, in der TPropertySourcesich die Klasse befindet, die die Eigenschaft enthält. Sie können die Funktion in TPropertySource generisch machen, um sie aus der Klasse zu extrahieren. Ich empfehle, sich den vollständigen Code der MVVM Foundation anzusehen .

Dan Bryant
quelle
Mit einem Beispiel für den Aufruf der Funktion ist dies sicherlich eine +1. Hoppla, ich habe nicht gesehen, dass es eine in der Debug-Behauptung gibt - deshalb ist es böse, einen Entwickler horizontal scrollen zu lassen, um zum wichtigen Teil einer Zeile zu gelangen;)
OregonGhost
Hmmm ... ich muss diesen sezieren, um ihn zu verstehen.
Jim C
Visual Studio 2008 kennzeichnet "TPropertySource" als Fehler ("kann nicht gefunden werden").
Jim C
Ich habe gerade festgestellt, dass es sich um einen Typnamen handelt, nicht nur um ein Symbol <T> wie in C ++. Was repräsentiert TPropertySource?
Jim C
2
Um diese Kompilierung public static string GetPropertyName<TPropertySource>(Expression<Func<TPropertySource, object>> expression)var name = GetPropertyName<TestClass>(x => x.Foo);
durchzuführen
16

Okay, hier ist, was ich letztendlich erstellt habe (basierend auf der Antwort, die ich ausgewählt habe, und der Frage, auf die er verwiesen hat):

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

Verwendung:

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

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

Die PropertyInfo- Klasse soll Ihnen dabei helfen, wenn ich das richtig verstehe.

  1. Type.GetProperties () -Methode

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

Ist das was du brauchst?

Will Marcouiller
quelle
Nein, obwohl ich GetProperties verwende, wenn die App die Anfrage nach "SomeSecret" erhält. Die App sucht in einer Karte nach "SomeSecret", um festzustellen, dass eine Eigenschaft namens "SomeProperty" in einer Klasse namens "SomeClass" gefunden werden muss.
Jim C
nameof (SomeProperty) erleichtert dies ab .net 4.0. Keine Notwendigkeit für so lange Hacks.
Div Tiwari
6

Mit Reflection können Sie die tatsächlichen Namen der Eigenschaften abrufen.

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

Wenn Sie einer Eigenschaft einen "String-Namen" zuweisen möchten, schreiben Sie ein Attribut, über das Sie nachdenken können, um den String-Namen zu erhalten.

[StringName("MyStringName")]
private string MyProperty
{
    get { ... }
}
Robert Harvey
quelle
1
Ja, so verarbeitet die App eingehende Anfragen nach "SomeSecret", aber es gibt mir kein Tool für das ExposeProperty-Problem.
Jim C
Interessant ... dann können Sie MyProperty nach Herzenslust umbenennen, solange Sie sich nicht mit MyStringName anlegen. Wenn Sie es aus irgendeinem Grund ändern möchten, müssen Sie den ExposeProperty-Parameter ändern. Zumindest könnte ich mit einer solchen Warnung einen Kommentar neben dem Attribut einfügen, da Sie ihn betrachten müssen, um den Wert des Attributs zu ändern (im Gegensatz zum Umbenennen einer Eigenschaft, das von jedem Referenzspeicherort aus erfolgen kann).
Jim C
6

Ich habe Ihre Lösung so geändert, dass sie mehrere Eigenschaften verkettet:

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

Verwendung:

string name = GetPropertyName(() => someObject.SomeProperty.SomeOtherProperty);
// returns "SomeProperty.SomeOtherProperty"
übermenschlich
quelle
4

Basierend auf der Antwort, die bereits in der Frage und in diesem Artikel enthalten ist: https://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/ I. präsentiere meine Lösung für dieses Problem:

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

Und ein Test, der auch die Verwendung zum Beispiel und statische Eigenschaften zeigt:

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

Alte Frage, aber eine andere Antwort auf diese Frage besteht darin, eine statische Funktion in einer Hilfsklasse zu erstellen, die das CallerMemberNameAttribute verwendet.

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

Und dann benutze es wie:

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

Sie können die StackTrace-Klasse verwenden, um den Namen der aktuellen Funktion abzurufen (oder wenn Sie den Code in eine Funktion einfügen, eine Ebene zurücktreten und die aufrufende Funktion abrufen).

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

Sprotty
quelle
Ich weiß nicht, wo Sie die Stapelverfolgung erfassen wollten, aber ich kann mir keine vorstellen, die den Namen der Eigenschaft enthalten würde.
Jim C
Sie können dies tun, dies kann jedoch aufgrund von Compiler-Inlining-Optimierungen zu unerwarteten Ergebnissen (einschließlich Ausnahmen) führen. smelser.net/blog/post/2008/11/27/…
JoeGeeky
0

Ich hatte einige Schwierigkeiten, die bereits für meinen speziellen Anwendungsfall vorgeschlagenen Lösungen zu verwenden, fand es aber schließlich heraus. Ich denke nicht, dass mein spezieller Fall eine neue Frage wert ist, deshalb veröffentliche ich meine Lösung hier als Referenz. (Dies hängt sehr eng mit der Frage zusammen und bietet eine Lösung für alle anderen, die einen ähnlichen Fall wie ich haben.)

Der Code, den ich erhalten habe, sieht folgendermaßen aus:

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

Der wichtige Teil für mich ist, dass CompanyControlder Compiler mir in der Klasse nur erlaubt, eine boolesche Eigenschaft auszuwählen ICompanyViewModel, die es anderen Entwicklern erleichtert, sie richtig zu machen.

Der Hauptunterschied zwischen meiner Lösung und der akzeptierten Antwort besteht darin, dass meine Klasse generisch ist und ich nur Eigenschaften des generischen Typs abgleichen möchte, die boolesch sind.

bikeman868
quelle
0

So habe ich es implementiert. Der Grund dafür ist, dass wenn die Klasse, von der Sie den Namen von ihrem Mitglied erhalten möchten, nicht statisch ist, Sie einen Instanse davon erstellen und dann den Namen des Mitglieds erhalten müssen. so generisch kommt hier um zu helfen

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

Die Verwendung ist wie folgt

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