如何获取应用了自定义属性的成员?

71

我正在使用C#创建一个自定义属性,并且我想根据该属性应用于方法还是属性来执行不同的操作。起初,我打算在自定义属性构造函数中执行new StackTrace().GetFrame(1).GetMethod()以查看调用属性构造函数的方法是什么,但现在我不确定那会给我什么结果。如果该属性应用于属性上会怎样?GetMethod()会返回一个MethodBase实例吗?在C#中有没有其他获取应用了属性的成员的方法?

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property,
    AllowMultiple = true)]
public class MyCustomAttribute : Attribute

更新:好的,我可能问错了问题。在自定义属性类中,我如何获取应用了我的自定义属性的成员(或包含该成员的类)?Aaronaught建议不要通过遍历堆栈来查找应用了我的属性的类成员,但是在我的属性构造函数内部,我还能以其他方式获取这些信息吗?


2
你有没有考虑过,如果你依赖于调用方法的属性,解决方案会变得多么脆弱?只需要有人决定从该方法中重构一些公共代码,就可以打破这个解决方案,而他们可能与你的运行时检查相距甚远,他们将不知道为什么它会崩溃,也无法找出原因。除非你编写自己的DI/AOP框架,否则我认为这很可怕... - Aaronaught
4
回答你的更新:你无法做到。属性不包含任何有关它们应用于哪种类型的信息;它们是反向工作的。通常,你只能从成员(属性或方法)的 GetCustomAttribute[s] 方法中获取属性的信息。 - Aaronaught
1
再加上一个“从这里到那里无法到达”的问题。 - Sky Sanders
3
嗯,我也遇到了同样的问题,但我不同意希望检测属性目标是糟糕的设计。考虑 DescriptionAttribute 的示例,该属性是子类化为返回与文化相关的资源字符串。理想情况下,资源键应源自应用 DescriptionAttribute 的属性/成员的名称。如果无法检测到目标,则该属性无法实现此目的。 - Mark
如何使用AttributeUsageAttribute AttributeTargets将子类/拆分为具有不同属性的单独属性? - user423430
显示剩余5条评论
4个回答

48

属性提供元数据,不知道它们正在装饰的东西(类、成员等)的任何信息。另一方面,被装饰的东西可以请求自己被哪些属性装饰。

如果您必须知道所装饰的东西的类型,则需要在属性的构造函数中显式传递它。

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, 
    AllowMultiple = true)] 
public class MyCustomAttribute : Attribute
{
   Type type;

   public MyCustomAttribute(Type type)
   {
      this.type = type;
   }
}

我已经阅读了很多关于这个信息,但如果它是有效的呢?C# 是否提供其他选项来解决这个问题?比如,在我的情况下,我需要验证登录的用户是否是公司经理,但是我不想一遍又一遍地编写相同的代码(甚至调用相同的方法),我宁愿创建一个自定义属性来处理并提供更清晰的代码。谢谢 :) - eestein
@eestein 你处于什么上下文环境中?如果你在一个Web应用程序中,我建议你使用Authorize属性和Identity框架代码。 - Scott Dorman
这是一个Web应用程序的后端。普通类。当我实例化该类时,我已经有了登录用户信息,我更喜欢使用某种注释而不是每次需要验证时调用方法。谢谢您的回复。 - eestein

43

因为关于堆栈帧和方法如何工作的混淆似乎很多,所以这里有一个简单的演示:

static void Main(string[] args)
{
    MyClass c = new MyClass();
    c.Name = "MyTest";
    Console.ReadLine();
}

class MyClass
{
    private string name;

    void TestMethod()
    {
        StackTrace st = new StackTrace();
        StackFrame currentFrame = st.GetFrame(1);
        MethodBase method = currentFrame.GetMethod();
        Console.WriteLine(method.Name);
    }

    public string Name
    {
        get { return name; }
        set
        {
            TestMethod();
            name = value;
        }
    }
}

该程序的输出将是:
``` set_Name ```
在C#中,属性是一种语法糖。它们编译成IL中的getter和setter方法,有些.NET语言甚至可能不认识它们是属性——属性解析完全是按约定进行的,IL规范中没有真正的规则。
现在,假设你有一个非常好的理由让程序想要检查自己的堆栈(实际上很少有这样的理由)。为什么你希望它对属性和方法的行为有所不同呢?
属性背后的全部理念是它们是一种元数据。如果你想要不同的行为,请将其编码到属性中。如果一个属性在应用于方法或属性时意义不同,那么你应该有两个属性。将第一个目标设置为`AttributeTargets.Method`,将第二个目标设置为`AttributeTargets.Property`。简单明了。
但是再次强调,从自己的堆栈中获取一些来自调用方法的属性是非常危险的。在某种程度上,你正在冻结程序的设计,使其更难以扩展或重构。这不是属性通常使用的方式。更合适的例子可能是验证属性。
public class Customer
{
    [Required]
    public string Name { get; set; }
}

然后您的验证器代码,对于实际传入的实体一无所知,可以执行以下操作:
public void Validate(object o)
{
    Type t = o.GetType();
    foreach (var prop in
        t.GetProperties(BindingFlags.Instance | BindingFlags.Public))
    {
        if (Attribute.IsDefined(prop, typeof(RequiredAttribute)))
        {
            object value = prop.GetValue(o, null);
            if (value == null)
                throw new RequiredFieldException(prop.Name);
        }
    }
}

换句话说,您正在检查给您的一个实例的属性,但您并不一定了解其类型。XML属性、数据合同属性,甚至是属性属性——.NET框架中几乎所有的属性都是这样使用的,用于实现某些与实例类型动态相关但与程序状态或堆栈上存在内容无关的功能。在创建堆栈跟踪时,很少有可能真正控制它。
因此,我再次建议您除非有非常充分的理由尚未告知我们,否则不要使用堆栈遍历方法。否则,您可能会发现自己处于一种痛苦的世界中。
如果您绝对必须(别说我们没警告过您),请使用两个属性,一个可应用于方法,另一个可应用于属性。我认为您会发现这比一个超级属性更容易使用。

5

自定义属性是通过某些代码调用ICustomAttributeProvider(反射对象)上的GetCustomAttributes方法来激活的,该反射对象表示应用属性的位置。因此,在属性的情况下,一些代码将获取该属性的PropertyInfo,然后在其上调用GetCustomAttributes。

如果您想构建一些验证框架,您需要编写检查自定义属性类型和成员的代码。例如,您可以拥有一个接口,该接口实现了属性以参与您的验证框架。可以简单地实现以下内容:

public interface ICustomValidationAttribute
{
    void Attach(ICustomAttributeProvider foundOn);
}

你的代码可以在(例如)一个类型上查找这个接口:
var validators = type.GetCustomAttributes(typeof(ICustomValidationAttribute), true);
foreach (ICustomValidationAttribute validator in validators)
{
     validator.Attach(type);
}

假设您将遍历整个反射图并为每个ICustomAttributeProvider执行此操作。在.NET FX中,类似方法的示例是WCF的“行为”(IServiceBehavior,IOperationBehavior等)。

更新:.NET FX确实具有一种类似于拦截框架的通用但基本未记录的形式,即ContextBoundObject和ContextAttribute。您可以搜索网络以获取使用它进行AOP的一些示例。


5

GetMethod总是会返回函数名。如果它是一个属性,你将会得到 get_PropertyName 或者 set_PropertyName

属性基本上就是一种方法,所以当你实现一个属性时,编译器会在生成的 MSIL 中创建两个单独的函数,即 get_ 和 set_ 方法。这就是为什么在堆栈跟踪中你会看到这些名称。


那么,'GetMethod'只是一个误称吗? - Sarah Vessels
但是你在堆栈跟踪中实际上并没有看到属性访问器,对吧?它应该只是调用GetCustomAttributes。 - alexdej
你会在堆栈跟踪中看到属性访问器,因为这实际上是在 IL 中被调用的方法。 - Aaronaught
嗯,这真的取决于您如何访问属性,正如@alexdej所指出的那样,当从GetCustomAttributes访问时,方法名称甚至不在堆栈跟踪中(在我的测试中)。 - Amirshk
GetCustomAttributes访问什么?堆栈跟踪并不是从属性本身执行的,而是从检查属性的代码执行的。属性只是元数据,除非在构造函数中进行可怕的不可言说的操作,否则它们不会真正被“执行”。 - Aaronaught
我在构造函数中检查了属性的堆栈跟踪。 - Amirshk

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