获取属性名称作为字符串

267

(请见下面我使用我接受的答案创建的解决方案)

我试图改善一些涉及反射的代码的可维护性。该应用程序具有一个.NET Remoting接口,公开了一个名为Execute的方法,用于访问未包含在其已发布的远程接口中的应用程序部分。

以下是应用程序如何指定属性(此示例中为静态属性),以便可以通过Execute访问:

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

因此,远程用户可以调用:

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

这个应用程序将使用反射来查找SomeClass.SomeProperty并将其作为字符串返回。

不幸的是,如果有人重命名了SomeProperty并忘记更改ExposeProperty()的第三个参数,它会破坏这个机制。

我需要相当于:

SomeClass.SomeProperty.GetTheNameOfThisPropertyAsAString()

有没有办法在ExposeProperty的第三个参数中使用,这样重构工具就可以处理重命名了。

好的,这是我最终创建的代码(基于我选择的答案和他引用的问题):

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

用法:

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

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

现在有了这个很酷的功能,是时候简化ExposeProperty方法了。打磨门把手是危险的工作...


10
非常感谢您添加了您的解决方案并做了总结。 - Simply G.
你应该将你的解决方案作为答案添加上去——它比你接受的答案更加简洁。 - Kenny Evitt
1
@Kenny Evitt:已完成 :) - Jim C
@JimC 点赞!并在当前被接受的答案的评论区中进行了链接。谢谢! - Kenny Evitt
继续向下滚动以获取真正的答案。我差点错过了它! - Joel Christophel
13个回答

626

在C# 6.0中,这不再是一个问题,因为您可以执行以下操作:

nameof(SomeProperty)

这个表达式在编译时会被解析为 "SomeProperty"

MSDN 上 nameof 的文档


23
这很酷,而且在使用 ModelState.AddModelError 调用时非常有用。 - Michael Silver
13
这是一个“常量字符串”!令人惊讶。 - Jack
6
如果你要编写微控制器程序,应该使用像C这样的低级语言;如果需要挤压每一个性能比如图像和视频处理,应该使用C或C++。但对于其他95%的应用程序来说,托管代码框架足够快了。最终,C#代码也会被编译成机器码,如果需要的话,甚至可以预编译成本机代码。 - Tsahi Asher
5
顺便说一下,@RaidenCore,你提到的应用程序早于C#出现,所以它们是用C++编写的。如果今天编写它们,谁知道会使用什么语言。例如看看Paint.NET。 - Tsahi Asher
4
当您想在WPF中使用RaiseProperty时,这非常有用!请使用RaisePropertyChanged(nameof(property))而不是RaisePropertyChanged("property") - Pierre
显示剩余8条评论

66

从这里 Retrieving Property name from lambda expression 使用 GetMemberInfo,你可以像下面这样做:

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

这太酷了。看起来它也适用于任何属性类型。 - Jim C
我刚刚尝试了实例属性和静态属性。目前为止还不错。 - Jim C
有没有想法在哪里可以获取包含 GetMemberInfo 的程序集或 NuGet 包?我在 Microsoft Enterprise Library 的“常用工具”包中找不到任何东西,而 MSDN 似乎表明该方法包含在其中。有一个“非官方”的包,但是非官方的并不令人鼓舞。JimC的答案基于这个答案,更加简洁,并且不依赖于一个看起来不可用的库。 - Kenny Evitt
1
@KennyEvitt,他所引用的方法是由他链接的问题的作者编写的。除了那种方法之外,您还可以使用此Type.GetMembers https://msdn.microsoft.com/en-us/library/system.type.getmembers%28v=vs.110%29.aspx的替代方法。 - Bon

22
好的,这是我最终创建的内容(基于我选择的答案和他参考的问题):
// <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;
 }

使用方法:

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

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

17

有一个众所周知的hack,可以从lambda表达式中提取它(这来自于Josh Smith的PropertyObserver类,在他的MVVM基础库中):

有一种广为人知的方法可以从 lambda 表达式中提取它(这是来自 Josh Smith 的 PropertyObserver 类,在他的 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;
    }

抱歉,这里缺少了一些上下文。这是一个更大的类的一部分,其中TPropertySource是包含属性的类。您可以将函数泛型化为TPropertySource,以从类中提取它。我建议查看来自MVVM Foundation的完整代码。


有了如何调用函数的示例,这肯定是加一分。哎呀,没看到调试断言中已经有一个了 - 这就是让开发人员水平滚动以到达行的重要部分是邪恶的原因 ;) - OregonGhost
嗯...我需要剖析一下才能理解它。 - Jim C
Visual Studio 2008将“TPropertySource”标记为错误(“找不到”)。 - Jim C
我刚意识到它是一个类型名称,而不仅仅是C++中的符号<T>。TPropertySource代表什么? - Jim C
2
为了使其编译通过,您只需更改方法签名以读取public static string GetPropertyName<TPropertySource>(Expression<Func<TPropertySource, object>> expression),然后像这样调用:var name = GetPropertyName<TestClass>(x => x.Foo); - dav_i
编辑答案添加这一信息会很有趣:为什么有些对象属性是UnaryExpression而另一些是MemberExpression? - Pedro Gaspar

10

如果我理解正确,PropertyInfo 类应该能帮助你实现这一点。

  1. Type.GetProperties() method

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

这是您需要的吗?


不是的,尽管当应用程序接收到“SomeSecret”的请求时,我确实使用GetProperties。 应用程序在映射中查找“SomeSecret”,以发现它需要在名为“SomeClass”的类中查找名为“SomeProperty”的属性。 - Jim C
nameof(SomeProperty) 从 .net 4.0 开始就能轻松解决这个问题,不需要使用那么长的 hack。 - Mantra

6

虽然这是一个老问题,但是另一种解决方法是创建一个帮助类中的静态函数,该函数使用CallerMemberNameAttribute。

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

然后像这样使用:

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

6
您可以使用反射来获取属性的实际名称。

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

如果你需要为属性分配一个“字符串名称”,为什么不编写一个属性,可以使用反射来获取字符串名称?
[StringName("MyStringName")]
private string MyProperty
{
    get { ... }
}

1
是的,这就是应用程序处理“SomeSecret”传入请求的方式,但它没有为ExposeProperty问题提供工具。 - Jim C
有趣的是……你可以随心所欲地重命名 MyProperty,只要不对 MyStringName 进行更改,如果出于某种原因你确实想要更改它,那么你需要修改 ExposeProperty 参数。至少我可以在属性旁边添加一个注释,提醒这种情况,因为要更改属性的值必须查看它(与重命名属性不同,可以从任何引用位置完成)。 - Jim C

6
我修改了您的解决方案,以便可以链式地使用多个属性:
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;
}

使用方法:

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

5

根据问题和这篇文章:https://handcraftsman.wordpress.com/2008/11/11/how-to-get-c-property-names-without-magic-strings/,我提供了解决方案:

我们可以使用表达式体成员(Expression-bodied members)特性来实现此目的。通过将属性名作为参数传递到Lambda表达式中,我们可以避免在代码中使用魔术字符串。

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: https://dev59.com/x3E85IYBdhLWcg3wXCEv
    /// </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;
    }
}

这是一个测试,同时展示了实例和静态属性的用法:

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

0

我不知道你想在哪里捕获堆栈跟踪,但我想不出一个包含属性名称的堆栈跟踪。 - Jim C
你可以这样做,但由于编译器内联优化,这可能会导致意外结果(包括异常)。http://www.smelser.net/blog/post/2008/11/27/Be-carefull-with-that-Stack-Trace.aspx - JoeGeeky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接