1.什么是Attribute

  特性简单点理解就是为目标元素添加一些附加信息,这些附加信息我们可以在运行期间以反射的方式拿到。目标元素指的是程序集、模块、类、参数、属性等元素,附加信息指的是特性类中的成员。可以看出特性类其实就是一个数据结构,我们可以将各种各样的信息放入这个类中,并将特性类关联到指定目标元素中,在目标元素中每关联一个特性就创建一个特性类的实例,当然它的作用还不止如此。下面是使用特性的3段代码,分别是3个类。第一个是MyAttribute特性类,第二个是与特性类关联的目标类MyClass,第3个类是主程序类Program。在第一个类中,我使用了AttributeUsage系统特性类,.net还为我们提供了很多固定特性类,比如Serializable、Conditional、DllImport、Flags等。AttributeUsage类的参数作用我代码里已经有了注释,可以添加这个特性也可以不添加,但这个特性类在我这个程序中必须添加。原因是我在第二个类MyClass中添加了4个MyAttribute特性实例,而默认情况下只允许添加一个实例。因此我得在AttributeUsage中指定AllowMultiple为true,如果不指定编译会报错。

  在AttributeUsage特性类的参数中,并不是完全以传值的形式创建的实例,其中还可以直接给参数赋值比如AllowMultiple和Inherited。对于前者一般是构造函数中的参数,我们必须给构造函数赋值否则就不能初始化了,因此这种类型的参数是固定参数。对于后者则是可选参数,细心点会发现它就是特性类中的属性,比如在第二个类MyClass中就给Hobby属性赋值了。正因为参数里有可选参数,故MyClass可以同时关联4个特性实例。在第三个类中,我通过GetCustomAttribute拿到定制的特性数组,其实就是MyAttribute的实例,这样就可以通过实例对象获取里面的数据成员了。这便是Attribute的基本使用,由于使用时需要调用构造函数,因此定制特性类必须有公共构造函数。将程序的exe文件放入Reflector中可以很清楚的看到特性就是一个类,使用特性其实就是调用特性的构造函数。

 //特性也可以用在特性上
[AttributeUsage(
AttributeTargets.All, //目标元素可以是任何元素
AllowMultiple=true, //多个特性可以加在一个元素上,为false时一个元素上只允许有这个特性的唯一实例
Inherited=true)] //特性可被子类继承,为false时不可以被继承
class MyAttribute : Attribute
{
//字段
public string name="默认名字";
public int age = ;
string hobby="默认爱好"; //属性
public string Hobby
{
get { return hobby; }
set { hobby = value; }
}
//构造方法
public MyAttribute()
{ }
public MyAttribute(string name, int age)
{
this.name = name;
this.age = age;
} //实例方法
public void haha()
{
Console.WriteLine("哈哈");
}
}
    [My()]
[My(Hobby="足球")]
[My("小方",)]
[My("小白",,Hobby="篮球")]
class MyClass
{
public void haha()
{
Console.WriteLine("我是MyClass类");
}
}
    class Program
{
static void Main(string[] args)
{
/*
* 本来想看一下会不会默认拿第一个实例,结果执行时报错:找到同一个类型的多个实例
//拿到MyClass上的一个特性实例
MyAttribute myAttribute = (MyAttribute)Attribute.GetCustomAttribute(typeof(MyClass), typeof(MyAttribute));
//看看这个实例是哪一个
if (myAttribute!=null)
{
Console.WriteLine(myAttribute.name);
Console.WriteLine(myAttribute.age);
Console.WriteLine(myAttribute.Hobby);
myAttribute.haha();
}
*/ //拿到MyClass上的特性实例数组,这里有4个MyAttribute的实例
MyAttribute[] myAttributes = (MyAttribute[])Attribute.GetCustomAttributes(typeof(MyClass), typeof(MyAttribute));
MyAttribute myAttribute = myAttributes[];
if (myAttribute != null)
{
Console.WriteLine(myAttribute.name);
Console.WriteLine(myAttribute.age);
Console.WriteLine(myAttribute.Hobby);
myAttribute.haha();
}
Console.ReadLine();
}
}
/*执行结果:
小白
30
篮球
哈哈
*/

 2.Attribute的作用

  因为特性的存在,让我们可以在程序运行时得到一些信息,再根据这些信息进行逻辑判断。比如可以使用特性来确保Model对象的数据全部去除空字符串,代码如下面所示。第一段代码指特性类MyAttribute,第二段代码指使用特性的MyClass类,第三段代码指MyClass类的扩展方法Trim,第四段代码指主程序类Program。在main方法中创建了一个myclass对象,在给这个对象赋值时特意指定了一些空格。假设现在需要将MyClass这个类作为数据库实体类People,它的实例存放着输入的数据,这个数据可能有空格。一般情况下我得调用trim()方法来去除空格,但是如果MyClass的属性很多的话那就很麻烦了,需要写很多ToString().Trim()方法。而使用特性+扩展方法则可以轻松很多,对于需要进行空格去除的属性添加一个MyAttribute特性,接着调用实例对象的Trim扩展方法。在Trim方法中,会遍历这个对象的所有属性,接着遍历每个属性的所有特性,并找到打了MyAttribute特性的属性,接着进行ToString().Trim()方法的调用并重新给属性赋值,这样只需写一句myclass.Trim()就可以实现除掉空格的功能。如果没有特性,虽然一样可以使用扩展方法来对属性进行去除空格,但是我们无法对指定的属性进行去除,只能一口气把所有类型为string的字符串空格全都去除。

 [AttributeUsage(AttributeTargets.Property,Inherited=false,AllowMultiple=false)]
public class TrimAttribute : Attribute
{
//字段与属性
readonly Type myType;
public Type MyType
{
get { return this.myType; }
}
//构造函数
public TrimAttribute(Type type)
{
myType = type;
}
}
 class MyClass
{
[TrimAttribute(typeof(string))]
public string Name { get; set; } [TrimAttribute(typeof(string))]
public string Hobby { get; set; } [TrimAttribute(typeof(string))]
public string Address { get; set; }
}
 //扩展方法必须是静态类,静态方法。
public static class TrimAttributeExtension
{
public static void Trim(this object obj)
{
Type t = obj.GetType();
//得到myclass实例对象的所有属性
foreach (PropertyInfo prop in t.GetProperties())
{
//得到某个属性上的所有特性
foreach(var attr in prop.GetCustomAttributes(typeof(TrimAttribute),true))
{
TrimAttribute trimAttribute = (TrimAttribute)attr;
//获得obj的prop属性的值
object o=prop.GetValue(obj, null);
//如果o不为null且这个属性上的特性实例的MyType属性是string类型
if (o!= null && (trimAttribute.MyType == typeof(string)))
{
//重新给这个属性赋值,也就是已经Trim()后的,可以看到GetPropertyValue(obj, prop.Name)其实就是o。
object newValue = GetPropertyValue(obj, prop.Name).ToString().Trim();
prop.SetValue(obj, newValue, null);
}
}
}
}
//拿到属性本身所表示的值
private static object GetPropertyValue(object instance, string propertyName)
{
//首先得到instance的Type对象,然后调用InvokeMember方法,
//这个方法的第一个参数意思是你需要调用的属性、方法、字段的”名字“,第二个参数是你调用propertyName是要干什么,
//这里是拿到属性,第四个是要操作的实例。最后是需要传入的参数,这里调用属性因此不需要参数我就设置为null了。
return instance.GetType().InvokeMember(propertyName, BindingFlags.GetProperty, null, instance, null);
}
}
 class Program
{
static void Main(string[] args)
{
MyClass myclass = new MyClass();
myclass.Name = "小方 ";
myclass.Hobby = " 篮球 ";
myclass.Address = " 湖北";
myclass.Trim(); /*
执行到这里会看到上面三个属性的值中空格全部都没有了
myclass.Name = "小方";
myclass.Hobby = "篮球";
myclass.Address = "湖北";
*/
Console.ReadLine();
}
}

3.总体上认识Attribute

  特性,这个描述信息的数据类所描述的信息其实就是元数据。当我们在VS中生成解决方案时,在debug文件夹中就会出现一个exe文件,在windows中它称为可迁移可执行文件PE。PE由3部分组成:PE标头、IL、元数据。PE头主要作用是标识此文件是PE文件并说明在内存中执行程序的入口点。IL不用多说,但有一点要注意IL指令中常有元数据标记。元数据包含元数据表和堆数据结构。一个程序中会有很多类,这些类在PE中都会记录在一个记录类型的元数据表中,此外还有记录方法、字段等成员的元数据表,元数据表也可以引用其他的表和堆。可将这些表理解为数据库中的表,表之间通过主外键来建立一种约束与联系。不过我不知道这些表是如何创建的,是程序中某种成员的所有数据全部放在一起,还是有些数据比如字段是以类为划分的。元数据的堆数据结构有4种,分别是字符串、Blob、用户字符串、GUID。在IL中还有一个元数据标记,可以理解为一个指向元数据的指针,它包含4字节。第一个字节说明这个指针指向的类型,比如是指向类表呢还是指向方法表呢。后3个字节说明指向目标表中的位置,这种感觉有点像zigbee编程。再来看元数据的作用,在程序中定义的所有成员以及外部引入的成员都将在元数据中进行说明,这样在JIT生成机器指令时正是通过元数据中的信息来完成即时编译的。元数据中存储程序中程序集的说明(名称、版本、依赖的其他程序集等),类型的说明(类成员、可访问性、继承实现关系等),特性。到这里可以理解特性是属于PE中的元数据的一部分,具体到物理结构上我觉得是有一个元数据特性表,比如类型元数据表的一个类有一个指针指向它的元数据特性表,这个特性表记录着与这个类关联的所有特性。另外由于特性是作为元数据的一部分,因此特性类将会在编译时就实例化,而不是运行期动态实例化。

声明:本文原创发表于博客园,作者为方小白 ,如有错误欢迎指出。本文未经作者同意不得转载,否则视为侵权。

C#基础之Attribute的更多相关文章

  1. C#基础系列——Attribute特性使用

    前言:上篇 C#基础系列——反射笔记 总结了下反射得基础用法,这章我们来看看C#的另一个基础技术——特性. 1.什么是特性:就博主的理解,特性就是在类的类名称.属性.方法等上面加一个标记,使这些类.属 ...

  2. 【基础】Attribute的妙用

    一.何为Attribute 下面是微软官方对Attribute的解释: 公共语言运行时允许你添加类似关键字的描述声明,叫做Attributes,它对程序中的元素进行标注,如类型.字段.方法和属性等.A ...

  3. C#基础系列——小话泛型

    前言:前面两章介绍了C#的两个常用技术:C#基础系列——反射笔记 和 C#基础系列——Attribute特性使用 .这一章来总结下C#泛型技术的使用.据博主的使用经历,觉得泛型也是为了重用而生的,并且 ...

  4. C#基础系列——委托和设计模式(二)

    前言:前篇 C#基础系列——委托实现简单设计模式 简单介绍了下委托的定义及简单用法.这篇打算从设计模式的角度去解析下委托的使用.我们知道使用委托可以实现对象行为(方法)的动态绑定,从而提高设计的灵活性 ...

  5. C#基础系列——再也不用担心面试官问我“事件”了

    前言:作为.Net攻城狮,你面试过程中是否遇到过这样的问题呢:什么是事件?事件和委托的区别?既然事件作为一种特殊的委托,那么它的优势如何体现?诸如此类...你是否也曾经被问到过?你又是否都答出来了呢? ...

  6. C#基础系列——异步编程初探:async和await

    前言:前面有篇从应用层面上面介绍了下多线程的几种用法,有博友就说到了async, await等新语法.确实,没有异步的多线程是单调的.乏味的,async和await是出现在C#5.0之后,它的出现给了 ...

  7. C#基础系列——一场风花雪月的邂逅:接口和抽象类

    前言:最近一个认识的朋友准备转行做编程,看他自己边看视频边学习,挺有干劲的.那天他问我接口和抽象类这两个东西,他说,既然它们如此相像, 我用抽象类就能解决的问题,又整个接口出来干嘛,这不是误导初学者吗 ...

  8. c#基础系列(转)

    转:http://www.cnblogs.com/landeanfen/p/4953025.html C#基础系列——一场风花雪月的邂逅:接口和抽象类 前言:最近一个认识的朋友准备转行做编程,看他自己 ...

  9. 【转载】C#基础系列——小话泛型

    前言:前面两章介绍了C#的两个常用技术:C#基础系列——反射笔记 和 C#基础系列——Attribute特性使用 .这一章来总结下C#泛型技术的使用.据博主的使用经历,觉得泛型也是为了重用而生的,并且 ...

随机推荐

  1. Effective Java 01 Consider static factory methods instead of constructors

    Advantage Unlike constructors, they have names. (BigInteger.probablePrime vs BigInteger(int, int, Ra ...

  2. SQL 相关

    SET STATISTICS TIME ON 记录查询的相关数据 生成随机Guid SELECT NewID() 按照某一列排序并生成序号 select Row_Number() OVER (ORDE ...

  3. Support for AMD usage of jwplayer (require js)

    使用require js 模块化代码时,其中播放器用的是jwplayer7.x 然后载入jwplayer.js后总是报license无效(license已经加入),最后在jwplayer官网论坛里找到 ...

  4. ELK 信息统计分析-2

    Range 按数值类型的字段聚合统计 { "query": { "match_all": {} }, "aggs": { "ter ...

  5. C#连接MySql数据库的方法

    1.要连接MySql数据库必须首先下载MySql的连接.net的文件, 文件下载地址为http://download.csdn.net/detail/xiaoliu123586/91455792.解压 ...

  6. c#分页读取GB文本文件

    应用场景: a.我在做BI开发测试的时候,有可能面对source文件数GB的情况,如果使用一般的文本编辑器,则会卡死,或要等很久才能显示出来. b.有时候,我们使用ascii(01)或ascii(02 ...

  7. pig 介绍与pig版 hello world

    前两天使用pig做ETL,粗浅的看了一下,没有系统地学习,感觉pig还是值得学习的,故又重新看programming pig. 以下是看的第一章的笔记: What is pig? Pig provid ...

  8. [转]Cordova + Ionic in Visual Studio - 101 Tutorial [Part I]

    本文转自:http://binarylies.ghost.io/cordova-ionic-in-visual-studio/ Hi everyone, I thought about lending ...

  9. Zookeeper C API 指南一(转)

    Zookeeper 监视(Watches) 简介 Zookeeper C API 的声明和描述在 include/zookeeper.h 中可以找到,另外大部分的 Zookeeper C API 常量 ...

  10. 通过JDBC进行简单的增删改查(二)

    本章笔记更易理解和学习,也是我第一次初学的笔记. package javastudy; import java.sql.Connection; import java.sql.DriverManage ...