【问题标题】:How do attribute classes work?属性类如何工作?
【发布时间】:2011-02-10 05:18:03
【问题描述】:

我的搜索一直只找到解释如何使用属性并将属性应用于类的指南。我想学习如何创建自己的属性类以及它们的工作原理。

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

属性类实例如何访问与它们关联的类? SerializableAttribute 类如何访问它所应用的类以便序列化其数据?它是否具有某种 SerializableAttribute.ThisIsTheInstanceIAmAppliedTo 属性? :) 或者它是否以相反的方向工作,每当我序列化某些东西时,我将 MyClass 实例传递给的 Serialize 函数将反射性地通过 Attributes 并找到 SerialiableAttribute 实例?

【问题讨论】:

  • +1 为大家提供有用的答案。现在我清楚地明白,属性本身没有任何行为,也不会改变它们所应用的类的行为。它们只是固定在类上的额外元数据。所以我认为的“行为”实际上不是属性类的代码,而只是其他代码,例如 Serialize 方法,解释元数据并使用它有条件地执行操作(例如查看 Serializable 属性的 Serialize 方法)。
  • 查看何时运行属性构造函数的好例子:stackoverflow.com/a/1168590/84206

标签: c# attributes


【解决方案1】:

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

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

[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!

   }     
}

【讨论】:

  • 我问这不是很难,而是为了提高我的理解:如果一个属性只是作为一个标志,它会有什么好处?其他设计可以做到这一点,例如 MyFancyClass 实现 IsSerializable 接口或具有 IsSerializable 属性的接口。什么是必需的属性? SerializableAttribute 类本身、该属性类本身中的代码以及该类中的数据/属性扮演什么角色?
  • @AaronLS 您可以在运行时添加属性。即使代码不知道如何处理它们,您也可以在读取/写入文件中的对象时保留它们。
【解决方案2】:

没有太多时间给你一个更完整的答案,但你可以找到已经使用反射应用于一个值的属性。至于创建它们,您从 Attribute Class 继承并从那里工作 - 您为属性提供的值将传递给 Attribute 类的构造函数。

已经有一段时间了,你可能会说...

马丁

【讨论】:

    【解决方案3】:

    属性本质上是元数据,可以附加到代码的各个部分。然后可以查询此元数据并影响某些操作的行为。

    属性几乎可以应用于代码的每个方面。例如,属性可以在程序集级别关联,如 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

    除此之外,您还可以定义自己的自定义属性,然后将这些属性应用于您的属性所针对的适用目标。然后在运行时,您的代码可以反映自定义属性中包含的值并采取适当的措施。

    举一个相当幼稚的例子,这只是为了举例:) 您可能想要编写一个持久性引擎,该引擎将自动将类映射到数据库中的表,并将类的属性映射到表列。您可以从定义两个自定义属性开始

    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)读取/写入对象。我想也许这些属性有某种“在编译时运行代码”。所以很长一段时间我都在想我可以用属性做什么很酷的事情。在调查之后,我意识到我的看法与事实相去甚远,但从未完全弄清楚属性是如何提供自定义“行为”的。
    【解决方案4】:

    认为属性是附加到类或方法定义(嵌入在程序集元数据中)的便利贴。

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

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

    您可能需要浏览this tutorial at MSDN,它回答了您的大部分问题,最后附有示例。尽管他们本可以提取一种名为 Audit(Type anyType); 而不是复制该代码。该示例通过检查属性来“打印信息”。但您可以以同样的方式做任何事情。

    【讨论】:

      【解决方案5】:

      我以前没有在日常工作中使用属性,但我读过它们。 我也做了一些测试,以支持我在这里说的话。如果我在任何地方错了 - 请随时告诉我:)

      据我所知,属性并不是常规类。当您创建应用它们的对象时,它们不会被实例化,不是一个静态实例,不是每个对象实例 1 个。 他们也没有访问他们所应用的类..

      相反,它们就像类的属性(属性?:P)。不像 .NET 类 properties,更像是“玻璃的一个属性是透明度”那种属性。您可以通过反射检查哪些属性应用于类,然后相应地对其进行操作。它们本质上是附加到类定义的元数据,而不是该类型的对象。

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

      例如在 Linq 表中,属性具有定义它们所引用的表/列的属性。但是这些类不使用这些属性。相反,DataContext 在将 linq 表达式树转换为 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 属性没有这些属性:))

      还有其他问题吗?欢迎提问!

      统一更新: 我看到你问过一个问题:为什么要使用它们? 作为一个例子,我将向您介绍 XML-RPC.NET 库。 您创建您的 XML-RPC 服务类,其方法将代表 xml-rpc 方法。现在主要的是:在 XmlRpc 中,方法名称可以有一些特殊字符,比如点。所以,你可以有一个 flexlabs.ProcessTask() xml rpc 方法。

      你可以这样定义这个类:

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

      这允许我以我喜欢的方式命名方法,同时仍然使用它必须的公共名称。

      【讨论】:

        【解决方案6】:

        如果您留意这个可下载的开源代码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));
        

        请考虑您放在方括号之间的属性始终是属性的构造函数。所以,如果你想有一个参数化的属性,你需要这样编写你的构造函数。

        此代码按原样提供,可能无法编译。它的目的是让您了解它的工作原理。

        确实,您通常希望类与属性具有不同的属性类。

        希望这会有所帮助!

        【讨论】:

          猜你喜欢
          • 2011-07-10
          • 2012-04-22
          • 1970-01-01
          • 1970-01-01
          • 2010-09-29
          • 2013-01-30
          • 2014-07-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多