在C#中获取属性的名称

39

给定以下类:

public class MyClass
{
    public int MyProperty {get; set;}
}

我该如何在代码中提取MyProperty的名称?

例如,我可以通过以下方式获取类的名称:

typeof(MyClass).Name
如何对属性做类似的操作?
问这个问题的原因是我希望这段特定的代码能够抵御名称重构。
编辑:所谓“抵御”,是指我希望调用代码在面对属性名更改时仍然具有强大的鲁棒性。我有些东西正在使用属性名的字符串表示形式。抱歉措辞不当。 我没有在问题中包含调用点代码,以使问题更清晰,避免进入其他关于调用点代码本质的讨论。

这是为了支持 INotifyPropertyChanged 吗? - user7116
只需使用反射,可以参考 Stack Overflow 上的示例,例如这个:https://dev59.com/u0jSa4cB1Zd3GeqPFGc- - Davide Piras
已经对您的问题和建议进行了编辑回答。 - Casper Leon Nielsen
2
你所描述的不是一个字段,而是一个属性。 - Boppity Bop
:) 这听起来非常傲慢,但我会忽略它,因为我是一个慷慨的人,我很抱歉你基于维基百科的教育。关于字段和属性,请阅读C#的定义。 - Boppity Bop
1
@BoppityBop 抱歉,今天过得不太好,纠正一下问题,我们讨论的是属性而不是字段。 - Casper Leon Nielsen
7个回答

62
在C# 6中,我们可以非常简单地完成它
nameof(MyField);

你可以以同样的方式获取方法、类型、属性、字段、类和命名空间的名称 例如:
 nameof(MyClass);
 nameof(namespacename1)  // returns "namespacename1"
 nameof(TestEnum.FirstValue) // returns enum's first value

MSDN参考

看这篇帖子


2
更好的解决方案。请确保,如果您正在使用持续集成(如Jenkins等),至少使用MSBuild版本14.0以便在那里进行编译,否则它只会在开发机器上进行编译。使用C# 6.0的好处。 - Edgar Froes
更好的多多。 - sertsedat
1
这是在编译时完成的。可能有理由希望在运行时动态执行,正如@John Leidegren所示。例如,如果使用混淆器等,则编译时名称将更改。 - rollsch
6
你为什么要给一个盗版答案点赞?看一下时间戳。 - Boppity Bop
StackOverflow是按照声望或时间戳显示答案的? - R.Akhlaghi

57
您可以使用编译器生成的表达式树来完成此操作,具体方法如下:
public static string GetMemberName<T, TValue>(Expression<Func<T, TValue>> memberAccess)
{
    return ((MemberExpression)memberAccess.Body).Member.Name;
}

现在从代码中调用静态方法:
class MyClass
{
    public int Field;
    public string Property { get; set; }
}

var fieldName = GetMemberName((MyClass c) => c.Field);
var propertyName = GetMemberName((MyClass c) => c.Property);
// fieldName has string value of `Field`
// propertyName has string value of `Property`

现在,您还可以使用重构来重命名该字段,而不会破坏此代码。


在Java中做类似的事情的机会有多大?还是这是C#独有的(就我目前所见)? - Vitali Pom
@VitaliPom 我不太了解Java。这是由C#编译器生成的。相当独特的C#内容,可能不是Java可以做的,但肯定是其他JVM语言可以做到的。如果不能,我会很惊讶的。 - John Leidegren
这个有多慢?值得进行某种缓存吗? - rollsch
3
@滚动缓慢且昂贵,但先衡量一下!如果可能,请使用C#的nameof特性,它可用于引用实例成员,例如nameof(MyClass.Field) => "Field" - John Leidegren

28

使用C# 6.0,您可以使用新的nameof运算符。

nameof(MyClass.MyField)  // returns "MyField"
nameof(MyClass)  //returns "MyClass"

请查看nameof (C# 和 Visual Basic 参考)获取更多示例。


10

使用反射,您可以找到所有MyClass的成员。

    MemberInfo[] members = typeof(MyClass).GetMembers();

现在您可以为每个会员找到所需的属性。

    foreach ( MemberInfo memberInfo in members)
    {
        Console.WriteLine("Name: {0}", memberInfo.Name); // Name: MyField
        Console.WriteLine("Member Type: {0}", memberInfo.MemberType); // Member Type: Property
    }
如果您只想查找属性,请使用 PropertyInfo 而不是 MemberInfo 。 或者写成这样
    foreach ( MemberInfo memberInfo in members.Where(p => p.MemberType == MemberTypes.Property))
    {
        Console.WriteLine("Name: {0}", memberInfo.Name); // Name: MyField
        Console.WriteLine("Member Type: {0}", memberInfo.MemberType); // Member Type: Property
    }

1
请考虑编辑您的帖子,添加更多关于代码功能和解决问题原理的解释。如果答案主要只包含代码(即使它能工作),通常也无法帮助提问者理解他们的问题。 - SuperBiasedMan
它按预期工作,只是不要忘记添加 using System.Linq; - Soner from The Ottoman Empire

5
您可以使用以下包含一个使用表达式树作为参数的方法的类来根据Lambda表达式确定成员名称:
public class MemberHelper<T>
{
    public string GetName<U>(Expression<Func<T, U>> expression)
    {
        MemberExpression memberExpression = expression.Body as MemberExpression;
        if (memberExpression != null)
            return memberExpression.Member.Name;

        throw new InvalidOperationException("Member expression expected");
    }
}

然后像这样使用它:
MemberHelper<MyClass> memberHelper = new MemberHelper<MyClass>();
string name = memberHelper.GetName(x => x.MyField);

使用实例是一个好主意,这样你就不必在所有地方重复类型名称,但类型约束是不必要的。例如,这为什么不能用于结构体? - John Leidegren
@CarstenKönig:你应该吗?我认为这没有意义。如果用户认为某些内容没有增加价值,他们应该自由地删除它们。然而,MemberHelper的想法可以帮助在调用站点获得明显更平滑的语法。这不是一个无用的答案。 - sehe
@John Leidegren - 你说得对,我最初编写这个类是为了与其他类一起使用,没有考虑到它也可以用于结构体。已经根据你的建议修改了答案。 - jdavies
@sehe - 好的,你说得有道理 - 我也经常遇到这种情况 - 如果你不幸在几秒钟之后发布了基本相同的想法,你会很快地被踩...这就是为什么我取消了我的99%准备好的答案,因为它与其他人的内容相同 - 在这种情况下一切都看起来很好,所以我删除了我的评论 ;) - Random Dev
这个答案和我标记的另一个一样好 - 它也解决了我的问题。他比我更快一分钟 ;) - Casper Leon Nielsen

3
如果你只想获取实例成员的名称,你可以使用更短的代码:
    public static string GetMemberName<TValue>(Expression<Func<TValue>> memberAccess)
    {
        return ((MemberExpression)memberAccess.Body).Member.Name;
    }

在类中使用它的方法如下:

    ReflectionTools.GetMemberName(() => _someInstanceVariableOrProperty)

1

如果你不想浪费时间,可以尝试以下方法。我知道会有人遇到同样的问题。

// Soner - tested!
foreach (PropertyDescriptor descriptor in TypeDescriptor.GetProperties(item))
{
    string name = descriptor.Name;              // Name
    object value = descriptor.GetValue(item);   // Value
    var type = descriptor.PropertyType;         // Type
    Console.WriteLine($"{name}={value}={type}");
}

不要忘记添加 using System.ComponentModel;

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