如何通过反射获取当前属性名?

42
我希望通过反射在所处属性中获取其名称。是否可能?
我的代码如下:
public CarType Car
{
    get { return (Wheel) this["Wheel"];}
    set { this["Wheel"] = value; }
}

因为我需要更多类似这样的属性,所以我想要做这样的事情:

public CarType Car
{
    get { return (Wheel) this[GetThisPropertyName()];}
    set { this[GetThisPropertyName()] = value; }
}

1
+1 当我实现System.Configuration.ApplicationSettingsBase时,我也对此感到困惑。 - Wim Coenen
8个回答

57

由于属性本质上只是方法,因此您可以这样做并清理 get_ 返回:

class Program
    {
        static void Main(string[] args)
        {
            Program p = new Program();
            var x = p.Something;
            Console.ReadLine();
        }

        public string Something
        {
            get
            {
                return MethodBase.GetCurrentMethod().Name;
            }
        }
    }

如果您对性能进行分析,您应该会发现 MethodBase.GetCurrentMethod() 比 StackFrame 快得多。在 .NET 1.1 中,您还会在发布模式下遇到 StackFrame 的问题(据我记得,它的速度要快3倍)。

话虽如此,我相信性能问题不会造成太大问题-不过 StackFrame 缓慢性的有趣讨论可以在这里找到。

如果您担心性能问题,另一个选择是创建 Visual Studio Intellisense 代码段,为您创建属性并创建与属性名称相对应的字符串。


有趣的是,我不知道GetCurrentMethod()。 - BlueMonkMN
1
有没有类似于这个的东西适用于“属性”? - Serj Sagan

12

你提出的例子有点让人困惑,除非我没理解。从C# 6.0开始,你可以使用nameof运算符。

public CarType MyProperty
{
    get { return (CarType)this[nameof(MyProperty)]};
    set { this[nameof(MyProperty)] = value]};
}

如果你已经有一个处理getter/setter的方法,你可以使用C#4.5的CallerMemberName属性,在这种情况下,你甚至不需要重复名称。

public CarType MyProperty
{
    get { return Get<CarType>(); }
    set { Set(value); }
}

public T Get<T>([CallerMemberName]string name = null)
{
    return (T)this[name];
}

public void Set<T>(T value, [CallerMemberName]string name = null)
{
    this[name] = value;
}

2
每次我都会忘记 nameof 的神奇之处。与其他解决方案相比,只需调用 nameof(property) 就容易得多了。确认一下,对于我来说,只做 nameof(item.PropertyName) 就可以完美地解决问题。尽管对于其他人来说可能会因情况而异。 - Niklas
1
@Niklas nameof 是 C# 中引入的最伟大的语法改进之一,几乎使在代码中使用字面字符串变得过时,从而不仅提高了方便性,还提高了代码的安全性。我一直都在使用它。 - Mike Fuchs

7
我想了解更多关于您需要的上下文,因为在属性访问器中,您似乎应该已经知道正在使用哪个属性。不过,如果必须的话,您可能可以使用MethodBase.GetCurrentMethod().Name并删除get_/set_之后的任何内容。
更新:
根据您的更改,我认为您应该使用继承而不是反射。我不知道您的字典中有什么数据,但我认为您真正想要的是不同的汽车类别,例如轿车、跑车、越野车、旅行车,而不是将类型保存在本地变量中。然后,您将具有方法实现,对于该类型的汽车执行适当的操作。与其找出您拥有什么类型的汽车,然后做某些事情,您只需调用适当的方法,汽车对象就会根据其类型执行正确的操作。
 public interface ICar
 {
      void Drive( decimal velocity, Orientation orientation );
      void Shift( int gear );
      ...
 }

 public abstract class Car : ICar
 {
      public virtual void Drive( decimal velocity, Orientation orientation )
      {
          ...some default implementation...
      }

      public abstract void Shift( int gear );

      ...
 }

 public class AutomaticTransmission : Car
 {
       public override void Shift( int gear )
       {
          ...some specific implementation...
       }
 }

 public class ManualTransmission : Car
 {
       public override void Shift( int gear )
       {
          ...some specific implementation...
       }
 }

我已经添加了更多关于我的背景的解释。 - Tom Smykowski
你的更新展示了为什么不应该尝试使用反射。使用建议的反射方式,你可能最终会发现自己在“GetThisPropertyName”中,并决定你是“ThisPropertyName”属性。你将需要调整示例以向下一堆栈调用,以获取实际的属性调用。虽然这是可能的,但看起来有点过度。你的代码将充斥着这些[GetThisPropertyName()]调用的复制/粘贴,而且它不会更可读(在我看来)。我个人会坚持使用正常的字符串字典访问器。 - Noam Gal

5

请使用MethodBase.GetCurrentMethod()代替!

反射用于处理在编译时无法完成的类型操作。获取属性访问器名称可以在编译时决定,因此您可能不应该使用反射。

您可以使用System.Diagnostics.StackTrace从调用堆栈中获取访问器方法的名称。

string GetPropertyName()
{
    StackTrace callStackTrace = new StackTrace();
    StackFrame propertyFrame = callStackTrace.GetFrame(1); // 1: below GetPropertyName frame
    string properyAccessorName = propertyFrame.GetMethod().Name;

    return properyAccessorName.Replace("get_","").Replace("set_","");
}

1
我一直在做这样的事情,但它卡住了我。请注意,如果属性被内联,可能会导致问题:尽管在getter/setter之前放置[MethodImpl(MethodImplOptions.NoInlining)]可以帮助解决问题,但这很不方便... - Kharlos Dominguez

3

我实现了一个类似于这样的系统:


    [CrmAttribute("firstname")]
    public string FirstName
    {
       get { return GetPropValue<string>(MethodBase.GetCurrentMethod().Name); }
       set { SetPropValue(MethodBase.GetCurrentMethod().Name, value); }
    }

    // this is in a base class, skipped that bit for clairty
    public T GetPropValue<T>(string propName)
    {
        propName = propName.Replace("get_", "").Replace("set_", "");
        string attributeName = GetCrmAttributeName(propName);
        return GetAttributeValue<T>(attributeName);            
    }

    public void SetPropValue(string propName, object value)
    {
        propName = propName.Replace("get_", "").Replace("set_", "");
        string attributeName = GetCrmAttributeName(propName);
        SetAttributeValue(attributeName, value);
    }

    private static Dictionary<string, string> PropToAttributeMap = new Dictionary<string, string>();
    private string GetCrmAttributeName(string propertyName)
    {
         // keyName for our propertyName to (static) CrmAttributeName cache
         string keyName = this.GetType().Name + propertyName;
         // have we already done this mapping?
         if (!PropToAttributeMap.ContainsKey(keyName))
         {
             Type t = this.GetType();
             PropertyInfo info = t.GetProperty(propertyName);
             if (info == null)
             {
                 throw new Exception("Cannot find a propety called " + propertyName);
             }

             object[] attrs = info.GetCustomAttributes(false);
             foreach (object o in attrs)
             {
                 CrmAttributeAttribute attr = o as CrmAttributeAttribute ;
                 if (attr != null)
                 {
                     // found it. Save the mapping for next time.
                     PropToAttributeMap[keyName] = attr.AttributeName;
                     return attr.AttributeName;
                 }
              }
              throw new Exception("Missing MemberOf attribute for " + info.Name + "." + propertyName + ". Could not auto-access value");
           }

           // return the existing mapping
           string result = PropToAttributeMap[keyName];
           return result;
        }

还有一个自定义属性类叫做CrmAttributeAttribute。

我强烈建议不要在解决方案中使用GetStackFrame(),我的原始版本的解决方案最初非常简洁:

return GetPropValue<string>();

但它比上面的版本慢了600倍。

2

解决方案 #1

var a = nameof(SampleMethod);    //a == SampleMethod
var b = nameof(SampleVariable);  //b == SampleVariable
var c = nameof(SampleProperty);  //c == SampleProperty

解决方案 #2

MethodBase.GetCurrentMethod().Name; // Name of method in which you call the code
MethodBase.GetCurrentMethod().Name.Replace("set_", "").Replace("get_", ""); // current Property

解决方案 #3

来自StackTrace:

public static class Props
{
    public static string CurrPropName => 
         (new StackTrace()).GetFrame(1).GetMethod().Name.Replace("set_", "").Replace("get_", "");

    public static string CurrMethodName => 
        (new StackTrace()).GetFrame(1).GetMethod().Name;
}

您只需要调用Props.CurrPropNameProps.CurrMethodName


解决方案 #4

.NET 4.5+的解决方案:

public static class Props
{
    public static string GetCallerName([System.Runtime.CompilerServices.CallerMemberName] String propertyName = "")
    {
         return propertyName;
    }
}

usage: Props.GetCallerName();


0

是的,它就是!

string test = "test string";
Type type = test.GetType();

PropertyInfo[] propInfos = type.GetProperties();
for (int i = 0; i < propInfos.Length; i++) 
{
    PropertyInfo pi = (PropertyInfo)propInfos.GetValue(i);
    string propName = pi.Name;
}

0
尝试使用 System.Diagnostics.StackTrace 反射调用堆栈。该属性应该在调用堆栈的某个位置(如果您直接从属性代码中调用它,则可能在顶部)。

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