属性类是如何工作的?

61

我的搜索结果只显示如何使用和应用属性到一个类中的指南。我想学习如何创建自己的属性类以及它们的机制。

属性类是如何实例化的? 它们是在应用它们的类实例化时实例化的吗?每个被应用它们的类实例化时都会实例化一个吗?举例来说,如果我将SerializableAttribute类应用于MyData类,并实例化5个MyData实例,那么在幕后是否会创建5个SerializbleAttribute类的实例?还是只有一个实例在所有实例之间共享?

属性类实例如何访问与其关联的类? SerializableAttribute类如何访问它所应用的类,以便对其进行序列化?它有一种像"SerializableAttribute.ThisIsTheInstanceIAmAppliedTo"属性吗?:)还是反过来工作,当我序列化某些东西时,我传递MyClass实例给Serialize函数,然后反射遍历Attributes并找到SerialiableAttribute实例?


7
+1 意味着赞同有帮助的答案。现在我清楚地了解到属性本身没有任何行为并且不会改变应用于它们的类的行为。它们只是附加到类上的额外元数据。因此,我所认为的“行为”实际上不是属性类的代码,而只是其他代码(例如Serialize方法)解释元数据并使用它有条件地执行操作(比如Serialize方法查看Serializable属性)。 - AaronLS
查看属性构造函数运行的好例子:https://dev59.com/nnM_5IYBdhLWcg3w6X1e#1168590 - AaronLS
6个回答

43

我以前在日常工作中没有使用过属性,但我读过一些关于它们的内容。同时我也做了一些测试来证实我在这里要说的内容。如果我在任何地方都是错的,请随时告诉我:

据我所知,属性不像普通类那样起作用。它们在你创建一个应用该属性的对象时不会被实例化,也不会有一个静态实例或每个对象实例都有一个实例。它们也不会访问应用该属性的类。

相反,它们就像类的属性(属性?:P)一样。并不像.NET类的属性,更像“玻璃的一个属性是透明性”这种属性。您可以从反射中检查应用到类上的属性,然后根据需要进行操作。它们本质上是附加到类定义的元数据,而不是该类型的对象。

您可以尝试获取类、方法、属性等的属性列表。当你获取这些属性列表时,它们将被实例化。然后您可以对这些属性中的数据进行操作。

例如,Linq表格、属性上有定义它们指向哪个表/列的属性。但这些类不使用这些属性。取而代之的是,当DataContext将查询表达式树转换为SQL代码时,它将检查这些对象的属性。

现在给出一些真实的例子..我在LinqPad中运行了它们,所以不用担心奇怪的Dump()方法。我已将其替换为Console.WriteLine,使代码更容易理解。

void Main()
{
    Console.WriteLine("before class constructor");
    var test = new TestClass();
    Console.WriteLine("after class constructor");

    var attrs = Attribute.GetCustomAttributes(test.GetType()).Dump();
    foreach(var attr in attrs)
        if (attr is TestClassAttribute)
            Console.WriteLine(attr.ToString());
}

public class TestClassAttribute : Attribute
{
    public TestClassAttribute()
    {
        DefaultDescription = "hello";
        Console.WriteLine("I am here. I'm the attribute constructor!");
    }
    public String CustomDescription {get;set;}
    public String DefaultDescription{get;set;}

    public override String ToString()
    {
        return String.Format("Custom: {0}; Default: {1}", CustomDescription, DefaultDescription);
    }
}

[Serializable]
[TestClass(CustomDescription="custm")]
public class TestClass
{
    public int Foo {get;set;}
}

这个方法在控制台中的结果是:

before class constructor
after class constructor
I am here. I'm the attribute constructor!
Custom: custm; Default: hello

Attribute.GetCustomAttributes(test.GetType())返回这个数组: (该表格显示了所有条目的所有可用列...因此,Serializable属性没有这些属性 :) ) LinqPad Attributes Array

还有其他问题吗?请随意提问!

更新: 我看到你问了一个问题:为什么要使用它们? 以XML-RPC.NET库为例。 您创建自己的XML-RPC服务类,并使用方法来表示xml-rpc方法。现在最重要的是:在XmlRpc中,方法名称可以具有某些特殊字符,例如句点。因此,您可以拥有一个flexlabs.ProcessTask() xml rpc方法。

您可以按如下方式定义此类:

[XmlRpcMethod("flexlabs.ProcessTask")]
public int ProcessTask_MyCustomName_BecauseILikeIt();

这使我可以按照自己的喜好命名方法,同时仍然使用公共名称,因为必须如此。


谢谢。您是如何创建一个 TestClass 并传递属性值 TestClass(CustomDescription="custm"),即使 TestClass 的构造函数不接受参数,请问 [TestClass(CustomDescription="custm")] 是如何实现的? - Avv

17

属性本质上是可以附加到代码的各种元数据。这些元数据可以被查询和影响某些操作的行为。

几乎可以将属性应用于您代码的每个方面。例如,可以在程序集级别关联属性,例如AssemblyVersion和AssemblyFileVersion属性,它们控制与程序集相关联的版本号。

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

例如,可以将Serializable属性应用于类型声明,将该类型标记为支持序列化。实际上,这个属性在CLR中具有特殊的含义,并且实际上作为指令直接存储在IL中的类型中,这被优化为存储为位标志,可以更有效地处理,还有一些此类的伪自定义属性。

其他属性可以应用于方法、属性、字段、枚举、返回值等等。您可以通过查看此链接,了解属性可以应用到哪些可能的目标。

http://msdn.microsoft.com/en-us/library/system.attributetargets(VS.90).aspx

此外,您可以定义自己的自定义属性,然后将它们应用于您的属性所针对的适用目标。然后在运行时,您的代码可以反映自定义属性中包含的值并采取适当的操作。

举个比较幼稚的例子,仅供参考:您可能希望编写一个持久性引擎,该引擎将自动将Classes映射到数据库中的表,并将Class的属性映射到表列。您可以从定义两个自定义属性开始。

TableMappingAttribute
ColumnMappingAttribute
你可以将这个应用于你的类中,作为示例,我们有一个人(Person)类。
[TableMapping("People")]
public class Person
{
  [ColumnMapping("fname")]
  public string FirstName {get; set;}

  [ColumnMapping("lname")]
  public string LastName {get; set;}
}

编译时,除了编译器生成自定义属性定义的额外元数据之外,几乎没有其他影响。但是,现在您可以编写一个PersistanceManager,动态检查Person类实例的属性并将数据插入到People表中,将FirstName属性中的数据映射到fname列,将LastName属性中的数据映射到lname列。

至于关于属性实例的问题,属性的实例不会为每个类实例创建。所有People的实例都将共享同一个TableMappingAttribute和ColumnMappingAttributes的实例。事实上,只有在第一次查询属性时才会创建属性实例。


@Chris +1 我曾经认为,序列化和ORM等使用的属性在编译时会执行某些非常神奇的操作,以便在运行时它能够知道如何将对象读/写到二进制/XML文件(序列化)或数据库(ORM)。我想也许这些属性有一些“在编译时运行的代码”。因此,很长一段时间我一直在思考如何使用属性来实现一些酷炫的功能。但是在深入研究后,我意识到我的看法与事实相去甚远,但我仍然没有完全弄清楚属性如何提供自定义“行为”。 - AaronLS

6

是的,它们会使用您提供的参数进行实例化。

属性并没有“访问”类。该属性附加到反射数据中类/属性的属性列表中。

[Serializable]
public class MyFancyClass
{ ... }

// Somewhere Else:

public void function()
{
   Type t = typeof(MyFancyClass);
   var attributes = t.GetCustomAttributes(true);

   if (attributes.Count(p => p is SerializableAttribute) > 0)
   {
       // This class is serializable, let's do something with it!

   }     
}

1
我问这个问题并不是为了刁难,而是为了提高我的理解:如果属性只充当标志,那么它的好处是什么?其他设计也可以做到这一点,比如如果MyFancyClass实现了一个IsSerializable接口,或者一个带有IsSerializable属性的接口。为什么需要属性?SerializableAttribute类本身、该属性类中的代码以及该类中的数据/属性分别扮演着什么角色? - AaronLS
1
@AaronLS 你可以在运行时添加属性。即使代码不知道如何处理它们,你也可以在读写文件中保留它们的能力。 - phkahler

6

将属性看作贴在类或方法定义上的便签(嵌入在程序集元数据中)。

然后,您可以编写一个处理器/运行器/检查器模块,通过反射接受这些类型,查找这些便签并以不同方式处理它们。这称为声明式编程。您声明某些行为,而不是在类型中编写代码。

  • 类型上的Serializable属性声明它构建为可序列化。然后,XmlSerializer可以接受此类对象并执行必要的操作。您使用正确的便签标记需要进行序列化/隐藏的方法。
  • 另一个例子是NUnit。 NUnit运行器查看目标程序集中定义的所有类的[TestFixture]属性,以识别测试类。然后,它查找标记有[Test]属性的方法以识别测试,并运行并显示结果。

您可能希望通过MSDN中的此教程,其中包含大多数问题的答案以及示例。虽然他们可以提取名为Audit(Type anyType);的方法,而不是重复该代码。该示例通过检查属性来“打印信息”,但您可以以同样的方式执行任何操作。


2
如果你查看这个可下载的开源代码 LINQ to Active Directory (CodePlex),你可能会发现 Attributes.cs 文件中的机制很有趣,Bart De Smet 在那里编写了他所有属性类的定义。我就是从那里学到了属性。

简而言之,你可以专门化 Attribute 类,并为你的需求编写一些专门的属性。

public class MyOwnAttributeClass : Attribute {
    public MyOwnAttributeClass() {
    }
    public MyOwnAttributeClass(string myName) {
        MyName = myName;
    }
    public string MyName { get; set; }
}

然后,您可以在任何需要使用 MyOwnAttributeClass 的地方使用它。它可以用于类定义或属性定义。
[MyOwnAttributeClass("MyCustomerName")]
public class Customer {
    [MyOwnAttributeClass("MyCustomerNameProperty")]
    public string CustomerName { get; set; }
}

然后,您可以通过反射来获取它,就像这样:
Attribute[] attributes = typeof(Customer).GetCustomAttribute(typeof(MyOwnAttributeClass));

考虑到你在方括号中放置的属性始终是你的属性的构造函数。因此,如果你想要一个带参数的属性,你需要将你的构造函数编写为这样。
这段代码是按原样提供的,可能无法编译。它的目的是让你了解它的工作原理。
事实上,你通常希望为类和属性使用不同的属性类。
希望这可以帮助你!

1

时间不够充分,无法给您完整的答案,但是您可以使用反射找到已应用于值的属性。至于如何创建它们,您需要从属性类继承并从那里开始工作-您提供给属性的值会传递给属性类的构造函数。

这段时间过去了,正如您所看到的...

Martin


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