67

我在 C# 中创建了一个自定义属性,我想根据属性是应用于方法还是属性来做不同的事情。起初我打算new StackTrace().GetFrame(1).GetMethod()在我的自定义属性构造函数中查看什么方法称为属性构造函数,但现在我不确定这会给我带来什么。如果将属性应用于属性会怎样?会GetMethod()返回MethodBase该属性的实例吗?是否有不同的方法来获取在 C# 中应用属性的成员?

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

更新: 好吧,我可能一直在问错误的问题。从自定义属性类中,如何获取应用了我的自定义属性的成员(或包含该成员的类)? Aaronaught建议不要在堆栈中查找应用了我的属性的类成员,但是我如何从属性的构造函数中获取这些信息?

4

4 回答 4

45

属性提供元数据,并且对它们正在装饰的事物(类、成员等)一无所知。另一方面,被装饰的东西可以要求它被装饰的属性。

如果您必须知道被装饰的事物的类型,则需要在其构造函数中将其显式传递给您的属性。

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

   public MyCustomAttribute(Type type)
   {
      this.type = type;
   }
}
于 2010-01-30T20:47:37.680 回答
42

由于堆栈框架和方法的工作方式似乎存在很多混淆,这里有一个简单的演示:

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 规范中实际上没有任何规则。

现在,让我们暂时假设您有一个非常好的理由让程序想要检查自己的堆栈(并且这样做的实际理由很少)。为什么你会希望它在属性和方法上表现得不同呢?

属性背后的全部原理是它们是一种元数据。如果您想要不同的行为,请将其编码到属性中。如果一个属性可能意味着两个不同的东西,这取决于它是应用于方法还是属性 - 那么你应该有两个 attributes。将目标设置在第一个 toAttributeTargets.Method和第二个 to 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 属性、数据协定属性,甚至 Attribute 属性 - .NET Framework 中的几乎所有属性都以这种方式使用,以实现一些与实例类型相关但与程序状态无关的动态功能或堆栈上发生了什么。在创建堆栈跟踪时,您实际上不太可能控制它。

因此,我将再次建议您不要使用堆栈遍历方法,除非您有充分的理由这样做,而您还没有告诉我们。否则,您可能会发现自己处于一个受伤的世界。

如果你绝对必须(不要说我们没有警告你),那么使用两个属性,一个可以应用于方法,一个可以应用于属性。我想你会发现它比单个超级属性更容易使用。

于 2010-01-30T19:43:12.073 回答
5

GetMethod将始终返回函数名称。如果它是一个属性,您将获得get_PropertyNameset_PropertyName

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

于 2010-01-30T18:46:36.003 回答
4

自定义属性由调用 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 的示例。

于 2010-01-30T19:05:10.163 回答