Think about it if there is a messaging system that has a method to send a short message to someone:
// title: 標(biāo)題;author:作者;content:內(nèi)容;receiverId:接受者Id public bool SendMsg(string title, string author, string content, int receiverId){ // Do Send Action }
We soon discovered that listing the parameters one by one in the parameter list of the method is scalable It's terrible. We'd better define a Message class to encapsulate the short message, and then pass a Message object to the method:
public class Message{ private string title; private string author; private string content; private int receiverId; // 略 } public bool SendMsg(Messag msg){ // Do some Action }
At this time, we should probably delete the old method and replace it with this more scalable SendMsg method . Unfortunately, we often can't, because this set of programs may be released as a set of APIs, and many client programs are already using the old version of the SendMsg() method. If we simply delete the old SendMsg() when updating the program method, then the client program using the old version of SendMsg() method will not work.
What should we do at this time? Of course we can do it through method overloading, so we don’t have to delete the old SendMsg() method. But if the new SendMsg() not only optimizes the passing of parameters, but also comprehensively optimizes the algorithm and efficiency, then we will be eager to inform the client program that there is now a new high-performance SendMsg() method available. , but at this time the client program does not know that a new SendMsg method already exists, so what should we do? We can call the programmer who maintains the client program or send him an email, but this is obviously not convenient enough. There is a way that once he compiles the project, as long as there is a call to the old version of the SendMsg() method, the compiler will be notified.
There are features available in .Net to do this. A property is an object that can be loaded into an assembly and its objects. These objects include the assembly itself, modules, classes, interfaces, structures, constructors, methods, method parameters, etc. Objects loaded with properties are called properties. The English name of the target
attribute is called Attribute. In some books, it is translated as "attribute"; in other books, it is translated as "attribute"; because usually we will contain get and/or set accessors The class members are called "properties" (English Property), so in this article I will use the term "property" to distinguish "properties" (Property).
Let’s take a look at this example to see how features solve the above problem: We can add the Obsolete feature to the old SendMsg() method to tell the compiler that this method is obsolete, and then when the compiler finds that there is a place in the program When using this method marked with Obsolete, a warning message will be given.
namespace Attribute { public class Message {} public class TestClass { // 添加Obsolete特性 [Obsolete("請使用新的SendMsg(Message msg)重載方法")] public static void ShowMsg() { Console.WriteLine("這是舊的SendMsg()方法"); } public static void ShowMsg(Message msg) { Console.WriteLine("新SendMsg()方法"); } } class Program { static void Main(string[] args) { TestClass.ShowMsg(); TestClass.ShowMsg(new Message()); } } }
Now run this code, we will find that the compiler gives a warning: warning CS0618: "Attribute.TestClass.ShowMsg()" is obsolete: "Please use the new SendMsg (Message msg) overloaded method". By using the attribute, we can see that the compiler gives a warning message to tell the client program that a new method is available for use. In this way, after the programmer sees this warning message, he will consider using the new SendMsg() method. .
Through the above example, we have roughly seen how to use the feature: first there is a pair of square brackets "[]", followed by the left square bracket "[" followed by the name of the feature, such as Obsolete, followed by a round bracket " ()". Unlike ordinary classes, these parentheses can not only write the parameters of the constructor, but also assign values ??to the attributes of the class. In the example of Obsolete, only the constructor parameters are passed.
Use constructor parameters. The order of parameters must be the same as the order in which the constructor is declared. All parameters are also called positional parameters (Positional Parameters) in the properties. Correspondingly, attribute parameters are also called named parameters (Named Parameters).
If you can’t define a feature yourself and use it, I don’t think you can understand the feature well. Let’s build a feature ourselves now. Suppose we have such a very common requirement: when we create or update a class file, we need to indicate when and by whom the class was created. In future updates, we also need to indicate when and by whom it was updated. You can You don’t have to record the updated content. What would you do in the past? Do you add comments to the class like this:
//更新:jayce, 2016-9-10, 修改 ToString()方法 //更新:pop, 2016-9-18 //創(chuàng)建:code, 2016-10-1 public class DemoClass{ // Class Body }
This can indeed be recorded, but if one day we want to record these Save it to the database for backup? Do you want to view the source files one by one, find these comments, and then insert them into the database one by one?
Through the definition of the above characteristics, we know that characteristics can be used to give types Add metadata (data that describes the data, including whether the data was modified, when it was created, and who created it. This data can be a class, method, or attribute). These metadata can be used to describe the type. Well this is where properties should come in handy. So in this example, the metadata should be: comment type ("update" or "create"), modifier, date, remark information (optional). The target type of the attribute is the DemoClass class.
According to the understanding of the metadata attached to the DemoClass class, we first create a class RecordAttribute that encapsulates the metadata:
public class RecordAttribute { private string recordType; // 記錄類型:更新/創(chuàng)建 private string author; // 作者 private DateTime date; // 更新/創(chuàng)建 日期 private string memo; // 備注 // 構(gòu)造函數(shù),構(gòu)造函數(shù)的參數(shù)在特性中也稱為“位置參數(shù)”。 public RecordAttribute(string recordType, string author, string date) { this.recordType = recordType; this.author = author; this.date = Convert.ToDateTime(date); } // 對于位置參數(shù),通常只提供get訪問器 public string RecordType { get { return recordType; } } public string Author { get { return author; } } public DateTime Date { get { return date; } } // 構(gòu)建一個屬性,在特性中也叫“命名參數(shù)” public string Memo { get { return memo; } set { memo = value; } } }
Note that the parameter date of the constructor must be a constant, Type type, or constant array, So the DateTime type cannot be passed directly.
這個類不光看上去,實(shí)際上也和普通的類沒有任何區(qū)別,顯然不能它因?yàn)槊趾竺娓藗€Attribute就搖身一變成了特性。那么怎樣才能讓它稱為特性并應(yīng)用到一個類上面呢?進(jìn)行下一步之前,我們看看.Net內(nèi)置的特性O(shè)bsolete是如何定義的:
namespace System { [Serializable] [AttributeUsage(6140, Inherited = false)] [ComVisible(true)] public sealed class ObsoleteAttribute : Attribute { public ObsoleteAttribute(); public ObsoleteAttribute(string message); public ObsoleteAttribute(string message, bool error); public bool IsError { get; } public string Message { get; } } }
首先,我們應(yīng)該發(fā)現(xiàn),它繼承自Attribute類,這說明我們的 RecordAttribute 也應(yīng)該繼承自Attribute類。 (一個特性類與普通類的區(qū)別是:繼承了Attribute類)
其次,我們發(fā)現(xiàn)在這個特性的定義上,又用了三個特性去描述它。這三個特性分別是:Serializable、AttributeUsage 和 ComVisible。Serializable特性我們前面已經(jīng)講述過,ComVisible簡單來說是“控制程序集中個別托管類型、成員或所有類型對 COM 的可訪問性”(微軟給的定義)這里我們應(yīng)該注意到:特性本身就是用來描述數(shù)據(jù)的元數(shù)據(jù),而這三個特性又用來描述特性,所以它們可以認(rèn)為是“元數(shù)據(jù)的元數(shù)據(jù)”(元元數(shù)據(jù):meta-metadata)。(從這里我們可以看出,特性類本身也可以用除自身以外的其它特性來描述,所以這個特性類的特性是元元數(shù)據(jù)。)
因?yàn)槲覀冃枰褂谩霸獢?shù)據(jù)”去描述我們定義的特性 RecordAttribute,所以現(xiàn)在我們需要首先了解一下“元元數(shù)據(jù)”。這里應(yīng)該記得“元元數(shù)據(jù)”也是一個特性,大多數(shù)情況下,我們只需要掌握 AttributeUsage就可以了,所以現(xiàn)在就研究一下它。我們首先看上面AttributeUsage是如何加載到ObsoleteAttribute特性上面的。
[AttributeUsage(6140, Inherited = false)]
然后我們看一下AttributeUsage的定義:
namespace System { public sealed class AttributeUsageAttribute : Attribute { public AttributeUsageAttribute(AttributeTargets validOn); public bool AllowMultiple { get; set; } public bool Inherited { get; set; } public AttributeTargets ValidOn { get; } } }
可以看到,它有一個構(gòu)造函數(shù),這個構(gòu)造函數(shù)含有一個AttributeTargets類型的位置參數(shù)(Positional Parameter) validOn,還有兩個命名參數(shù)(Named Parameter)。注意ValidOn屬性不是一個命名參數(shù),因?yàn)樗话瑂et訪問器,(是位置參數(shù))。
這里大家一定疑惑為什么會這樣劃分參數(shù),這和特性的使用是相關(guān)的。假如AttributeUsageAttribute 是一個普通的類,我們一定是這樣使用的:
// 實(shí)例化一個 AttributeUsageAttribute 類 AttributeUsageAttribute usage=new AttributeUsageAttribute(AttributeTargets.Class); usage.AllowMultiple = true; // 設(shè)置AllowMutiple屬性 usage.Inherited = false;// 設(shè)置Inherited屬性
但是,特性只寫成一行代碼,然后緊靠其所應(yīng)用的類型(目標(biāo)類型),那么怎么辦呢?微軟的軟件工程師們就想到了這樣的辦法:不管是構(gòu)造函數(shù)的參數(shù) 還是 屬性,統(tǒng)統(tǒng)寫到構(gòu)造函數(shù)的圓括號中,對于構(gòu)造函數(shù)的參數(shù),必須按照構(gòu)造函數(shù)參數(shù)的順序和類型;對于屬性,采用“屬性=值”這樣的格式,它們之間用逗號分隔。于是上面的代碼就減縮成了這樣:
[AttributeUsage(AttributeTargets.Class, AllowMutiple=true, Inherited=false)]
可以看出,AttributeTargets.Class是構(gòu)造函數(shù)參數(shù)(位置參數(shù)),而AllowMutiple 和 Inherited實(shí)際上是屬性(命名參數(shù))。命名參數(shù)是可選的。將來我們的RecordAttribute的使用方式于此相同。(為什么管他們叫參數(shù),我猜想是因?yàn)樗鼈兊氖褂梅绞娇瓷先ジ袷欠椒ǖ膮?shù)吧。)假設(shè)現(xiàn)在我們的RecordAttribute已經(jīng)OK了,則它的使用應(yīng)該是這樣的:
[RecordAttribute("創(chuàng)建","張子陽","2008-1-15",Memo="這個類僅供演示")] public class DemoClass{ // ClassBody } //其中recordType, author 和 date 是位置參數(shù),Memo是命名參數(shù)。
從AttributeUsage特性的名稱上就可以看出它用于描述特性的使用方式。具體來說,首先應(yīng)該是其所標(biāo)記的特性可以應(yīng)用于哪些類型或者對象。從上面的代碼,我們看到AttributeUsage特性的構(gòu)造函數(shù)接受一個 AttributeTargets 類型的參數(shù),那么我們現(xiàn)在就來了解一下AttributeTargets。
AttributeTargets 是一個位標(biāo)記,它定義了特性可以應(yīng)用的類型和對象。
public enum AttributeTargets { Assembly = 1, //可以對程序集應(yīng)用屬性。 Module = 2, //可以對模塊應(yīng)用屬性。 Class = 4, //可以對類應(yīng)用屬性。 Struct = 8, //可以對結(jié)構(gòu)應(yīng)用屬性,即值類型。 Enum = 16, //可以對枚舉應(yīng)用屬性。 Constructor = 32, //可以對構(gòu)造函數(shù)應(yīng)用屬性。 Method = 64, //可以對方法應(yīng)用屬性。 Property = 128, //可以對屬性 (Property) 應(yīng)用屬性 (Attribute)。 Field = 256, //可以對字段應(yīng)用屬性。 Event = 512, //可以對事件應(yīng)用屬性。 Interface = 1024, //可以對接口應(yīng)用屬性。 Parameter = 2048, //可以對參數(shù)應(yīng)用屬性。 Delegate = 4096, //可以對委托應(yīng)用屬性。 ReturnValue = 8192, //可以對返回值應(yīng)用屬性。 GenericParameter = 16384, //可以對泛型參數(shù)應(yīng)用屬性。 All = 32767, //可以對任何應(yīng)用程序元素應(yīng)用屬性。 }
因?yàn)锳ttributeUsage是一個位標(biāo)記,所以可以使用按位或“|”來進(jìn)行組合。所以,當(dāng)我們這樣寫時:
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)
意味著既可以將特性應(yīng)用到類上,也可以應(yīng)用到接口上。
AllowMutiple 屬性用于設(shè)置該特性是不是可以重復(fù)地添加到一個類型上(默認(rèn)為false),就好像這樣:
[RecordAttribute("更新","jayce","2016-1-20")] [RecordAttribute("創(chuàng)建","pop","2016-1-15",Memo="這個類僅供演示")] public class DemoClass{ // ClassBody }
Inherited 就更復(fù)雜一些了,假如有一個類繼承自我們的DemoClass,那么當(dāng)我們將RecordAttribute添加到DemoClass上時,DemoClass的子類也會獲得該特性。而當(dāng)特性應(yīng)用于一個方法,如果繼承自該類的子類將這個方法覆蓋,那么Inherited則用于說明是否子類方法是否繼承這個特性。
現(xiàn)在實(shí)現(xiàn)RecordAttribute應(yīng)該是非常容易了,對于類的主體不需要做任何的修改,我們只需要讓它繼承自Attribute基類,同時使用AttributeUsage特性標(biāo)記一下它就可以了(假定我們希望可以對類和方法應(yīng)用此特性):
[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method, AllowMultiple=true, Inherited=false)] public class RecordAttribute:Attribute { // 略 }
我們已經(jīng)創(chuàng)建好了自己的自定義特性,現(xiàn)在是時候使用它了。
[Record("更新", "code", "2016-1-20", Memo = "修改 ToString()方法")] [Record("更新", "jayce", "2016-1-18")] [Record("創(chuàng)建", "pop", "2016-1-15")] public class DemoClass { public override string ToString() { return "This is a demo class"; } } class Program { static void Main(string[] args) { DemoClass demo = new DemoClass(); Console.WriteLine(demo.ToString()); } }
利用反射來查看 自定義特性信息 與 查看其他信息 類似,首先基于類型(本例中是DemoClass)獲取一個Type對象,然后調(diào)用Type對象的GetCustomAttributes()方法,獲取應(yīng)用于該類型上的特性。當(dāng)指定GetCustomAttributes(Type attributeType, bool inherit) 中的第一個參數(shù)attributeType時,將只返回指定類型的特性,否則將返回全部特性;第二個參數(shù)指定是否搜索該成員的繼承鏈以查找這些屬性。
class Program { static void Main(string[] args) { Type t = typeof(DemoClass); Console.WriteLine("下面列出應(yīng)用于 {0} 的RecordAttribute屬性:" , t); // 獲取所有的RecordAttributes特性 object[] records = t.GetCustomAttributes(typeof(RecordAttribute), false); foreach (RecordAttribute record in records) { Console.WriteLine(" {0}", record); Console.WriteLine(" 類型:{0}", record.RecordType); Console.WriteLine(" 作者:{0}", record.Author); Console.WriteLine(" 日期:{0}", record.Date.ToShortDateString()); if(!String.IsNullOrEmpty(record.Memo)){ Console.WriteLine(" 備注:{0}",record.Memo); } } } }

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)