自定义特性

在说自定义之前,有必要先介绍一些基本的概念.

元数据:就是C#中封装的一些类,无法修改,类成员的特性被称为元数据中的注释

1.什么是特性?

(1)属性和特性的区别

属性:属性是面向对象思想里所说的封装在类里面的数据字段,Get,Set方法.

特性:就相当于类的元数据.

来看看官方解释?

特性是给指定的某一声明的一则附加的声明性信息。 允许类似关键字的描述声明。它对程序中的元素进行标注,如类型、字段、方法、属性等。从.net角度看,特性是一种 类,这些类继承于System.Attribute类,用于对类、属性、方法、事件等进行描述,主要用在反射中。但从面向对象的级别看,其实Attribute是类型级别的,而不是对象级别。

Attribute和.net文件的元数据保存在一起,可以用来向运行时描述你的代码,或者在程序运行的时候影响程序的行为.

2.特性的应用

(1).net中特性用来处理多种问题,比如序列化,程序的安全特性,防止即时编译器对程序代码进行优化从而代码容易调试等等.定值特性的本质上是在一个类的元素上添加附加信息,并在运行其通过反射得带该附加信息(在使用数据实体对象时经常用到)

(2)Attribute作为编译期的指令时的应用

Conditional起条件编译的作用,只有满足条件,才允许编译器对它的代码进行编译.一般在程序调试的时候使用.

DllImport:用来标记非.net函数,表明该方法在一个外部的DLL定义.

Obsolete:这个属性用来标记当前的方法已被废弃,不再使用.

注意:Attribute是一个类,因此DllImport也是一个类,Attribute类是在编译的时候实例化,而不是想通常那样在运行时实例化.

CLSCompliant:保证整个程序集代码遵守CLS,否则编译将报错.

3.自定义特性

使用AttributeUsage,来控制如何应用新定义的特性.

[AttributeUsageAttribute(AttributeTargets.All  可以应用到任何元素

,AllowMultiple=true, 允许应用多次,我们的定值特性能否被重复放在同一个程序实体前多次。

,Inherited=false,不继承到派生

)]

特性也是一个类,必须继承于System.Attribute类,命名规范我类名+Attribute.不管直接还是间接继承,都会成为一个特性类,特性类的声明定义了一种可以放置在声明之上新的特性.使用[]语法使用自定义特性,可以使用反射来查看自定义特性.

案例:

public class MyselfAttribute:System.Attribute

不过说实话,特性确实常用到,但是自定义特性几乎用不到,貌似老外喜欢用.

如果不能自己定义一个特性并使用它,我姓你肯定觉得我在忽悠你,假设我们有这样一个很常见的需求:我们在创建或者更新一个类文件时,需要说要这个类时什么时候,由谁创建的,在一行的更新中还要说明在什么时候由谁更新的,可以记录也可以不记录更新的内容,以往的情况你会怎么做?肯定想到了添加注释:

//更新:张三,2015-2-3,修改了ToString()方法

//更新:李四,2014-4-5

//创建:王五,2011-7-8

public class DemoClass

{

//dosomething

}

这样做没问题,想要啥,就写啥,看起来很好啊,借用金星老师的一句话就是完美!

but,如果我们有一天想把这些记录保存到数据库中作为备份呢?你是不是要一条一条的去查看源文件,找出注释,然后在一条条的插入到数据库中呢?

通过上面特性的定义,我们知道特性可以用来给类型添加元数据(描述书觉得数据,包括数据是否被修改,何时创建,创建人,这些数据可以是一个类,方法,属性),这些元数据可以用于描述类型.那么在此处,特性就会派上用场.那么在本例中,元数据应该是:注释类型(更行或者创建),修改人,日期,备注信息(可有可无).而特性的目标类型是DemoClass类.

按照对于附加到DemoClass类上的元数据的理解,我们先创建一个封装了元数据的类:

public class RecordAttribute

{

private string recordType;//记录类型:更新或者创建

private string author;//作者

private DateTime data;//日期

private string memo;//备注

//构造函数的参数在特性中也称为"位置参数"

public RecordAttribute(string  recordType,string author,string  date)

{

this.recordType = recordType;

this.author = author;

this.data = Convert.ToDateTime(date);

}

//对于位置参数,通常只提供get访问

public string RecordType { get { return recordType; } }

public string Author { get { return author; } }

public DateTime Date { get { return Date; } }

//构建一个属性,在特性中也叫"命名参数"

public string Memo {

get { return memo; }

set { memo = value; }

}

}

注意:构造函数的参数date,必须为一个常量,Type类型,或者是常量数组,所以不能直接传递DateTime类型.

你会说,这不就是一个类吗?你不能因为后面跟了一个Attribute就摇身一变成了特性.那么这样才能然他成为特性并应用到一个类上面呢?进行下一步之前,咱先来看看.net内置的特性Obsolete是如何定义的:

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; }

}

}

添加特性的格式(位置参数和命名参数)

首先,我们应该能够发现,他继承自Attribute,这说明我们的RecordAttribute也应该继承自Attribute类.(一个特性类和普通类的区别是:继承了Attribute类)

其次,我们发现在这个特性的定义上,又用了三个特性去描述他.这三个特性分别是:Serializable,AttributeUsage和ComVisible .  Serializable和ComVisible特性比较简单,就是一个标志,AttributeUsage比较重要,有三个重要的参数可以设置,上面说了.

这里我们应该可以注意到:特性本身就是用来描述数据的元数据,而这三个特性又用来描述特性,所以他们是”元数据的元数据”(元元数据).

从这里我们可以看出,特性类本身也可以用除自身以外的其他特性来描述,所以这个特性类的特性数元元数据.

隐隐我们需要使用元元数据全无描述我们定义的特性RecordAttribute,所以现在我们需要首先了解一下”元元数据”.这里应该记得”元元数据”也是一个特性,大多数情况下,我们只需要掌握AttributeUsage就够了.现在我们深入的研究一下它.先来看一下上面AttributeUsage是如何加载到ObsoleteAttribute特性上面的.

[AttributeUsage(6140,Inheritedfalse)]

看看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; }

}

}

可以看到,头一个构造函数,这个构造函数含有一个AttributeTargets类型的位置参数(Positional Parameter)validOn,还有两个命名参数(Named Parameter).注意ValidOn属性不是一个命名参数,因为他不包含set访问器,是位置参数.

这里可能有有疑惑,为啥会这样划分参数,这和特性的使用是相关的,加入AttributeUsageAttribute是一个普通的类,我们一定会这样使用:

//实例化AttributeUsageAttribute类

AttributeUsageAttribute usage=new AttributeUsageAttribute(AttributeTargets.Class);

usage.AllowMultiple=true;//设置AllowMultiple属性

usage.Inherited=false;//设置Inherited属性

但是,特性只写成一行代码,然后紧靠其所应用的类型(目标类型),那么咋办呢?大牛们想到了:不管是构造函数的参数还是属性,彤彤写到构造函数的圆括号中,对于构造函数的参数,必须按照构造函数参数的顺序和类型;对于属性,采用”属性=值”这样的格式,他们之间用逗号分隔.于是上面的代码就缩减成了下面这样:

[AttributeUsage(AttributeTargets.Class,AllowMutliple=true,Inherited=false)]

可以看出,AttributeTargets.Class是构造函数的参数(位置参数),而AllowMutliple和Inherited实际上是属性(命名参数).命名参数是可选的.将来我们的RecordAttribute的使用方式于此相同.(为什么管这些属性叫做作数,可能是因为它们的使用方式看上去更像方法的参数)

假设现在我们的RecordAttribute已经OK了,则它的使用使用应该是这样的:

[RecordAttribute(“创建”,”syx”,”2015-8-8”,Memo=”hello,world”)]

public class DemoClass

{

//dosomething

}

其中recordType,author和date是位置参数,Memo是命名参数

C#自定义特性:AttributeTarget位标记

从AttributeUsage特性的名称上可以看出它用于描述特性的使用方式.具体来说,首先应该是其所标记的特性可以应用于哪些类型或者对象.从上面的代码中可以看到AttributeUsage特性的构造函数接受一个AttributeTargets类型的参数,那么我们现在就来了解一下AttributeTargets.

AttributeTargets是一个位标记,他定义了特性可以应用的类型和对象.

[Flags]

public enum AttributeTargets {

Assembly = 1,         //可以对程序集应用属性。

Module = 2,              //可以对模块应用属性。

Class = 4,            //可以对类应用属性。

Struct = 8,              //可以对结构应用属性,即值类型。

Enum = 16,            //可以对枚举应用属性。

Constructor = 32,     //可以对构造函数应用属性。

Method = 64,          //可以对方法应用属性。

Property = 128,           //可以对属性 (Property) 应用属性 (Attribute)。

Field = 256,          //可以对字段应用属性。

Event = 512,          //可以对事件应用属性。

Interface = 1024,            //可以对接口应用属性。

Parameter = 2048,            //可以对参数应用属性。

Delegate = 4096,             //可以对委托应用属性。

ReturnValue = 8192,             //可以对返回值应用属性。

GenericParameter = 16384,    //可以对泛型参数应用属性。

All = 32767,  //可以对任何应用程序元素应用属性。

}

上述例子中使用的是:

[AttributeUsage(AttributeTargets.Class,AllowMutiple=true,Inherited=false)]

而ObsoleteAttribute特性中加载的AttributeUsage是这样的:

[AttributeUsage(6140,Inherited=false)]

因为AttributeUsage是一个标记,所以可以使用按位或”|”来进行组合.so,当我们这样写的时候:

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface)

意味着既可以将特性应用到类上,也可以应用到接口上.

注意:这里存在这两个特例,观察上面的AttributeUsage的定义,说明特性还可以加载到程序集Assembly和模块Module上,而这两个属于我们的编译结果,在程序中并不存在这样的类型,我们该如何加载呢?可以使用这样的语法:[assembly:SomeAttribute(parameter list)],另外这条语句必须位于程序语句开始之前。

C#自定义特性:实现RecordAttribute

现在实现RecordAttribute应该很轻松了,对于类的主题不需要进行任何的修改,我们只是需要让这个类继承自Attribute类,同时使用AttributeUsage特性标记一下它就可以了(假定我们希望可以对类和方法应用此特性):

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple=true,Inherited=false)]

public class recordAttribute:Attribute

{

//主体

}

完整代码:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading.Tasks;

namespace 自定义特性

{

class Program

{

static void Main(string[] args)

{

DemoClass demo = new DemoClass();

Console.WriteLine(demo.ToString());

Console.ReadKey();

}

}

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = false)]

public class RecordAttribute : Attribute

{

private string recordType;//记录类型:更新或者创建

private string author;//作者

private DateTime data;//日期

private string memo;//备注

//构造函数的参数在特性中也称为"位置参数"

public RecordAttribute(string recordType, string author, string date)

{

this.recordType = recordType;

this.author = author;

this.data = Convert.ToDateTime(date);

}

//对于位置参数,通常只提供get访问

public string RecordType { get { return recordType; } }

public string Author { get { return author; } }

public DateTime Date { get { return Date; } }

//构建一个属性,在特性中也叫"命名参数"

public string Memo

{

get { return memo; }

set { memo = value; }

}

}

[Record("更新", "wangwu", "2008-1-20", Memo = "修改 ToString()方法")]

[Record("更新", "lisi", "2008-1-18")]

[Record("创建", "zhangsan", "2008-1-15")]

public class DemoClass

{

public override string ToString()

{

return "hello,world!";

}

}

}

这段程序可能简单的输出”hello,world”.我们的属性也好像使用”//”来注释一样对程序没有任何影响,实际上,我们添加的数据已经作为元数据添加到程序集中.

至此,一个完整的自定义特性的使用已经完成了,举个例子帮助你理解特性打个比方:你约一个没见过面的网友约会,约好时间地点,怎么解决不认识TA的问题?你们可以约好,手上拿个特别的东西不就解决了。这个特别的、用于标识你所不认识的人的东西,就相当于Attribute了。所以Attribute是用于在运行期动态调用的场合。

如果仅仅是前面介绍的内容,还是不足以说明Attribute有什么实用价值的话,那么从后面的章节开始我们将介绍几个Attribute的不同用法,相信你一定会对Attribute有一个新的了解。

C#编程(七十一)---------- 自定义特性的更多相关文章

  1. C#反射与特性(七):自定义特性以及应用

    目录 1,属性字段的赋值和读值 2,自定义特性和特性查找 2.1 特性规范和自定义特性 2.2 检索特性 3,设计一个数据验证工具 3.1 定义抽象验证特性类 3.2 实现多个自定义验证特性 3.3 ...

  2. 《手把手教你》系列技巧篇(七十一)-java+ selenium自动化测试-自定义类解决元素同步问题(详解教程)

    1.简介 前面宏哥介绍了几种关于时间等待的方法,也提到了,在实际自动化测试脚本开发过程,百分之90的报错是和元素因为时间不同步而发生报错.本文介绍如何新建一个自定义的类库来解决这个元素同步问题.这样, ...

  3. FreeSql (三十五)CodeFirst 自定义特性

    比如项目内已经使用了其它 orm,如 efcore,这样意味着实体中可能存在 [Key],但它与 FreeSql [Column(IsPrimary = true] 不同. Q: FreeSql 实体 ...

  4. 孤荷凌寒自学python第七十一天开始写Python的第一个爬虫

    孤荷凌寒自学python第七十一天开始写Python的第一个爬虫 (完整学习过程屏幕记录视频地址在文末) 在了解了requests模块和BeautifulSoup模块后,今天开始真正写一个自己的爬虫代 ...

  5. 【.net 深呼吸】自定义特性(Attribute)的实现与检索方法

    在.net的各个语言中,尤其是VB.NET和C#,都有特性这一东东,具体的概念,大家可以网上查,这里老周说一个非标准的概念——特性者,就是对象的附加数据.对象自然可以是类型.类型成员,以及程序集. 说 ...

  6. 解剖SQLSERVER 第七篇 OrcaMDF 特性概述(译)

    解剖SQLSERVER 第七篇  OrcaMDF 特性概述(译) http://improve.dk/orcamdf-feature-recap/ 时间过得真快,这已经过了大概四个月了自从我最初介绍我 ...

  7. C#自定义特性实例

    元数据,就是C#中封装的一些类,无法修改.类成员的特性被称为元数据中的注释. 1.什么是特性   (1)属性与特性的区别  属性(Property):属性是面向对象思想里所说的封装在类里面的数据字段, ...

  8. Shader的自定义特性使用

    使用自定义特性关键字,可以动态对Shader某一部分代码进行开关操作 shader(定义了KEYWORD1特性): 定义:#pragma shader_feature KEYWORD1 判断:#ifd ...

  9. C# 通过自定义特性 实现根据实体类自动创建数据库表

    .Net新手通常容易把属性(Property)跟特性(Attribute)搞混,其实这是两种不同的东西 属性指的类中封装的数据字段:而特性是对类.字段.方法和属性等元素标注的声明性信息 如下代码(Id ...

随机推荐

  1. 基于python的k-s值计算

    做评分卡模型时(假设有多个自变量,因变量即是否违约.)通常需要筛选变量. k-s值的作用类似于AUC,它期初是用来评价模型(变量)对是否违约事件的区分程度的. # -*- coding: utf-8 ...

  2. Dotfuscator使用

    参考:https://www.cnblogs.com/xiezunxu/articles/7228741.html

  3. 深入理解【缺页中断】及FIFO、LRU、OPT这三种置换算法

    缺页中断(英语:Page fault,又名硬错误.硬中断.分页错误.寻页缺失.缺页中断.页故障等)指的是当软件试图访问已映射在虚拟地址空间中,但是目前并未被加载在物理内存中的一个分页时,由中央处理器的 ...

  4. 自己的vim配置

    nmap <F11> :source ~/.vimrc<CR> "n 普通模式 F11映射为 :source ~/.vimrc winpos 5 5 "wi ...

  5. spring-boot集成spring-data-jpa

    参考这个就行, http://blog.csdn.net/wazz753/article/details/72472411 ps:集成过程中pom文件,我加入的内容如下,两个都需要,实体类记得加注解和 ...

  6. 小丸工具箱FAQ

    下载地址:https://maruko.appinn.me/index.html 本文章是把一些使用小丸工具箱中常见的操作失误或出错的问题集中写出并提出解决方法,以便大家寻找解决并避免重复提问. 文章 ...

  7. 微信WebView关闭后本地cookie无法清除问题

    问题背景 在微信WebView下的页面中登录后,关闭WebView返回后再次进入页面,发现登录态还存在,原因是微信不会主动清除cookie以及其他的缓存. 期望是关闭窗口后会清除cookie,重新进入 ...

  8. Jquery empty() remove() detach() 方法的区别

    方法简介: empty() This method removes not only child (and other descendant) elements, but also any text ...

  9. HDU Tody HDU2112

    不想用floyd了 也不一定适合  floyd只能处理小数据 dijkstra算法 wa了很久   一个是dijkstra里面的u   导致RE了无数次   下标溢出 还有就是注意细节  当起点和终点 ...

  10. JQuery框架2.位置属性|筛选方法|事件

    1.位置属性 jquery的css position获取匹配元素相对父元素的偏移位置:offset获取匹配元素在当前视口的相对偏移,返回的对象包含两个整型属性:top 和 left $("p ...