上一篇我们对系统的Attributes进行了MSIL代码的查看,了解到了其本质就是一个类的构造函数。本章我们将编写自己的Attributes。
首先我们定义书的属性代码,如下:
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] class BookAttribute : Attribute { private string bookName; private string bookAuthor; private int bookPrice; public int year = 1990; public int version = 0; public BookAttribute(string bookname, string bookauthor, int bookprice) { bookName = bookname; bookAuthor = bookauthor; bookPrice = bookprice; } public string BookName { get { return bookName; } } public string BookAuthor { get { return bookAuthor; } } public int BookPrice { get { return bookPrice; } } }
写自己的Attribute其实非常简单,首先你的类需要直接或者间接地继承自系统类Attribute。我们查看Attribute类就会发现该类只有两大类的方法,获取Attribute和IsDefined。
其次,使用AttributeUsage这个Attribute,非常有意思。查看AttributeUsage类,发现构造函数的参数只有一个AttributeTargets,就是Attribute的限制范围,这是系统定义的枚举,我们稍后再做详解。暂时限制只在指定的代码域和类上才能用BookAttribute。AllowMultiple参数指定了是否允许同一代码区间多次使用Attribute,Inherited则指定该类的子类是否继承该属性。
紧接着我们写一个体育书的类:
[BookAttribute("SportsBook", "sports", 10)] public class SportsBook { }
在这里,BookAttribute就和一个构造函数一样调用,构造函数的参数顺序不能调换,不能省略,类型也必须匹配,这些参数因此得名“定位参数”。
我们发现在BookAttribute里面还有一些变量,他们有默认值,如果我们一定要给这些变量赋值,可以写在定位参数的后面,如下:
[BookAttribute("SportsBook", "sports", 10, version = 1, year = 1995)] public class SportsBook { }
这些参数的名字叫命名参数,顾名思义。对于这类参数,我们可以完全不写,也可以写其中的某几个变量,当然,调换顺序也是完全没问题的,但注意不能和定位参数调换顺序。
接下来我们利用反射来取Attribute的值,代码如下:
static void Main(string[] args) { System.Reflection.MemberInfo memberinfo = typeof(SportsBook); BookAttribute attribute = (BookAttribute)Attribute.GetCustomAttribute(memberinfo, typeof(BookAttribute)); if (null != attribute) { Console.WriteLine(attribute.BookName); Console.WriteLine(attribute.BookAuthor); Console.WriteLine(attribute.BookPrice); Console.WriteLine(attribute.version); Console.WriteLine(attribute.year); } Console.ReadKey(); }
利用反射稍微繁琐了一点,但总体流程很清晰。运行程序后得到结果:
可以看到,Attribute和继承有点像,但是我们在SportsBook类内部是无法直接使用Attribute的变量的。
如果我们在SportsBook上放多个Attribute会怎么样?
代码如下:
[BookAttribute("SportsBook", "sports", 10, year = 1995, version = 1)] [BookAttribute("ArtBook", "art", 20, year = 1999)] public class SportsBook { }
直接运行程序,系统报错了~
当使用GetCustomAttribute获取Attribute的时候发生了语义异常。修改我们的Main方法:
static void Main(string[] args) { System.Reflection.MemberInfo memberinfo = typeof(SportsBook); Attribute[] attribute = Attribute.GetCustomAttributes(memberinfo, typeof(BookAttribute)); for (int i = 0; i < attribute.Length; ++i) { var attr = attribute[i]; if (attr is BookAttribute) { var battr = (BookAttribute)attr; Console.WriteLine(battr.BookName); Console.WriteLine(battr.BookAuthor); Console.WriteLine(battr.BookPrice); Console.WriteLine(battr.version); Console.WriteLine(battr.year); } }
}
我们使用了GetCustomAttributes的方法,该方法返回一个Attribute的数组。通过遍历数组我们能够取到我们定义的所有属性。输出如下:
可以看到写在越下面的Attribute在数组中的位置更靠前。
继续改造我们的SportsBook类:
[BookAttribute("SportsBook", "sports", 10, year = 1995, version = 1)] public class SportsBook { [BookAttribute("ArtBook", "art", 20, year = 1999)] public string bookName; }
我们在类内部增加了一个变量bookName,虽然和BookAttribute的变量重名了,但并没有影响。把BookAttribute的作用范围改为All,所以我们能把属性直接加到变量的字段上面。依然使用获取Attribute数组的方法,跑我们的程序:
可以看到并没有取得类内部的Attribute。修改Main方法:
static void Main(string[] args) { System.Reflection.MemberInfo memberinfo = typeof(SportsBook); Attribute[] attribute = Attribute.GetCustomAttributes(memberinfo, typeof(BookAttribute)); System.Reflection.PropertyInfo propertyInfo = typeof(SportsBook).GetProperty("bookName"); BookAttribute bookattr = (BookAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(BookAttribute)); for (int i = 0; i < attribute.Length; ++i) { var attr = attribute[i]; if (attr is BookAttribute) { var battr = (BookAttribute)attr; Console.WriteLine(battr.BookName); Console.WriteLine(battr.BookAuthor); Console.WriteLine(battr.BookPrice); Console.WriteLine(battr.version); Console.WriteLine(battr.year); } } Console.WriteLine("----------------分割线------------------"); Console.WriteLine(bookattr.BookName); Console.WriteLine(bookattr.BookAuthor); Console.WriteLine(bookattr.BookPrice); Console.WriteLine(bookattr.version); Console.WriteLine(bookattr.year); Console.ReadKey(); }
继续利用反射获取单个变量的property,并从该property上获得了对应的attribute。运行代码如下:
至此,我们自己的Attribute写完了。
回头再看看AttributeTargets的定义:
public enum AttributeTargets { // 摘要: // 可以对程序集应用属性。 Assembly = 1, // // 摘要: // 可以对模块应用属性。 Module = 2, // // 摘要: // 可以对类应用属性。 Class = 4, // // 摘要: // 可以对结构应用属性,即值类型。 Struct = 8, // // 摘要: // 可以对枚举应用属性。 Enum = 16, // // 摘要: // 可以对构造函数应用属性。 Constructor = 32, // // 摘要: // 可以对方法应用属性。 Method = 64, // // 摘要: // 可以对属性 (Property) 应用属性 (Attribute)。 Property = 128, // // 摘要: // 可以对字段应用属性。 Field = 256, // // 摘要: // 可以对事件应用属性。 Event = 512, // // 摘要: // 可以对接口应用属性。 Interface = 1024, // // 摘要: // 可以对参数应用属性。 Parameter = 2048, // // 摘要: // 可以对委托应用属性。 Delegate = 4096, // // 摘要: // 可以对返回值应用属性。 ReturnValue = 8192, // // 摘要: // 可以对泛型参数应用属性。 GenericParameter = 16384, // // 摘要: // 可以对任何应用程序元素应用属性。 All = 32767, }
可以看到基本区分段都有了,二进制移位的赋值,也能让我们直接使用与或操作来达到自己想要的效果。注意经常用的Field字段指代类或者结构体里的区域。
下一章节我们将探索Unity中定义好的各个Attribute。