一、初识特性

特性(attribute)是被指定给某一声明的一则附加的声明性信息。

设计类型的时候可以使用各种成员来描述该类型的信息,但有时候我们可能不太愿意将一些附加信息放到类的内部,因为这样,可能会给类型本身的信息描述带来麻烦或误解。我们想为类型、属性、方法及返回值附加额外的信息,这些附加信息可以更明确的表达类及其对象成员的状态,怎么办?定制特性Attribute可以做到。

在C#中,有一个小的预定义特性集合。在学习如何建立我们自己的定制特性(custom attributes)之前,我们先来看看在我们的代码中如何使用预定义特性。

 1 using System; 
2 public class AnyClass
3 {
4 [Obsolete("Don't use Old method, use New method", true)]
5 static void Old( ) { }
6 static void New( ) { }
7 public static void Main( )
8 {
9 Old( );
10 }
11 }

我们先来看一下上面这个例子,在这个例子中我们使用了Obsolete特性,它标记了一个不应该再被使用的程序实体。第一个参数是一个字符串,它解释了为什么该实体是过时的以及应该用什么实体来代替它。实际上,你可以在这里写任何文本。第二个参数告诉编译器应该把使用这个过时的程序实体当作一种错误。它的默认值是false,也就是说编译器对此会产生一个警告。

当我们尝试编译上面这段程序的时候,我们将会得到一个错误:   

AnyClass.Old()' is obsolete: 'Don't use Old method, use New method'

二、特性是什么

定制特性其实是一个类型的实例,为了符合“公共语言规范”CLS的要求,定制特性类必须直接或间接从公共抽象类System.Atrribute派生。查看文档发现StructLayoutAttribute,MarshalAsAttribute,DllImportAttribute,InAttribute和OutAttribute,这些类都是从System.Attribute派生。所有符合CLS规范的特性类都肯定从这个类派生。

三、定义自己的特性

开发定制特性(custom attributes)

现在让我们来看看如何开发我们自己的特性。
    首先我们要从System.Attribute派生出我们自己的特性类(一个从System.Attribute抽象类继承而来的类,不管是直接还是间接继承,都会成为一个特性类。特性类的声明定义了一种可以被放置在声明之上新的特性)。

1 using System; 
2 public class HelpAttribute : Attribute
3 {
4 }

  不管你是否相信,我们已经建立了一个定制特性,现在我们可以用它来装饰现有的类就好像上面我们使用Obsolete attribute一样。

1 [Help()] 
2 public class AnyClass
3 {
4 }

  注意:对一个特性类名使用Attribute后缀是一个惯例。然而,当我们把特性添加到一个程序实体,是否包括 Attribute后缀是我们的自由。编译器会首先在System.Attribute的派生类中查找被添加的特性类。如果没有找到,那么编译器会添加 Attribute后缀继续查找。

到目前为止,这个特性还没有起到什么作用。下面我们来添加些东西给它使它更有用些。

 1 using System; 
2 public class HelpAttribute : Attribute
3 {
4 public HelpAttribute(String Descrition_in)
5 {
6 this.description = Description_in;
7 }
8 protected String description;
9 public String Description
10 {
11 get
12 {
13 return this.description;
14 }
15 }
16 }
17 [Help("this is a do-nothing class")]
18 public class AnyClass
19 {
20 }

在上面的例子中,我们给HelpAttribute特性类添加了一个属性并且在后续的部分中我们会在运行时环境中查寻它。

四、限制特性

在定义特性时,我们有时会希望他只应用于枚举,或只应用于类,有时我们希望他多少出现,或只出现一次,为了告诉编译器空上特性的合法应用范围,需要向特性类应用System.AttributeUsageAttribute类的实例

AttributeUsage类是另外一个预定义特性类,它帮助我们控制我们自己的定制特性的使用。它描述了一个定制特性如和被使用。 
    AttributeUsage有三个属性,我们可以把它放置在定制属性前面。

ValidOn 
    通过这个属性,我们能够定义定制特性应该在何种程序实体前放置。一个属性可以被放置的所有程序实体在AttributeTargets enumerator中列出。通过OR操作我们可以把若干个AttributeTargets值组合起来。
    AllowMultiple 
    这个属性标记了我们的定制特性能否被重复放置在同一个程序实体前多次。

Inherited 
    我们可以使用这个属性来控制定制特性的继承规则。它标记了我们的特性能否被继承。

下面让我们来做一些实际的东西。我们将会在刚才的Help特性前放置AttributeUsage特性以期待在它的帮助下控制Help特性的使用。

 1 using System; 
2 [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
3 public class HelpAttribute : Attribute
4 {
5 public HelpAttribute(String Description_in)
6 {
7 this.description = Description_in;
8 }
9 protected String description;
10 public String Description
11 {
12 get
13 {
14 return this.description;
15 }
16 }
17 }

先让我们来看一下AttributeTargets.Class。它规定了Help特性只能被放在class的前面。这也就意味着下面的代码将会产生错误:

1 [Help("this is a do-nothing class")] 
2 public class AnyClass
3 {
4 [Help("this is a do-nothing method")] //error
5 public void AnyMethod()
6 {
7 }
8 }

编译器报告错误如下:

AnyClass.cs: Attribute 'Help' is not valid on this declaration type. 
    It is valid on 'class' declarations only.

我们可以使用AttributeTargets.All来允许Help特性被放置在任何程序实体前。可能的值是:

Assembly,Module,Class,Struct,Enum,Constructor,Method,Property,Field,Event,Interface,Parameter,Delegate

All
= Assembly | Module | Class | Struct | Enum | Constructor | Method |
Property | Field | Event | Interface | Parameter | Delegate

ClassMembers = Class | Struct | Enum | Constructor | Method | Property | Field | Event | Delegate | Interface

下面考虑一下AllowMultiple = false。它规定了特性不能被重复放置多次。

1 [Help("this is a do-nothing class")] 
2 [Help("it contains a do-nothing method")]
3 public class AnyClass
4 {
5 [Help("this is a do-nothing method")] //error
6 public void AnyMethod()
7 {
8 }
9 }

它产生了一个编译期错误。 
    AnyClass.cs: Duplicate 'Help' attribute

Ok,现在我们来讨论一下最后的这个属性。Inherited, 表明当特性被放置在一个基类上时,它能否被派生类所继承。

1 [Help("BaseClass")] 
2 public class Base
3 {
4 }
5 public class Derive : Base
6 {
7 }

五、检测定制特性

仅仅定义特性类没有用,确实可以定义自己想要的所有特性类,并应用自己想要的所有实例,但这样除了在程序集中生成额外的元数据,没有其它任何意义。应用程序代码的行为不会有任何改变。现在我们用一种称为反射的技术检测特性的存在,假定你现在让你

根据AnyClass类中是否应用了Help类型的实例来区别重写toString()方法:

public override string ToString()
{
if (this.GetType().IsDefined(typeof(HelpAttribute), false))
{
//如果是,就执行代码
}
else
{
//如果不是,就执行代码
}
}

上述代码调用Type的IsDefined方法,要求系统查看枚举类型的元数据,检查是否关联了HelpAttribute类的实例,如果IsDefined返回true,表明HelpAttribute的一个实例已与枚举类型关联

 六、定制特性的使用场景------“数据修改日志”

1、假设有对象person,我们需求是:记录(最终)人员信息中Address的修改情况,并且能够查询出所有status="valid"的人员的Address信息的变化情况;

public class Person
{
public int Id;
public string status;
public Address;
...
}

2、基本原理是:通过给实体类的定制的Attribute修饰,而在数据DAO的时候注入保存日志的动作来实现.
为了修饰实体类,区分需要保存的修改日志的实体类,系统中定义了两个Attribute类:AuditLogClassAttribute,AuditLogAttribute类,分别用于修饰实体类和类的属性,其构造函数和属性包括:

public AuditLogClassAttribute:Attribute
{
AuditLogClassAttribute(string name, string idProperty,bool logInserting,bool logDeleting,bool logChanging)
{
...
}
Public string Name{...}
public string IDProperty{}
} //用于修饰属性的Attribute
[AttributeUsage( AttributeTargets.Property,AllowMultiple=false)]
public class AuditLogAttribute : Attribute
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="name">保存到日志库时属性的名称</param>
/// <param name="alwaysLogging">是否总是记录该属性的值,无论它是否发生变更</param>
public AuditLogAttribute(string name,bool alwaysLogging)
{
...
} }

其中 AuditLogClassAttribute用于修饰类,Name:保存修改日志时该实体的名称,IDProperty:标志该实体的唯一性的属性名称。bool logInserting,bool logDeleting,bool logChanging用于标识是否在插入,删除,修改的时候是否记录。 AuditLogAttribute用于修饰属性.AlwaysLogging属性在用于类的属性时,是否总是记录它的值(无论是否发生了变化),缺省为false

3、例子如下:

[AuditLogClassAttribute("人员库","Xmbm",true,true,true]
public class Person
{
public int Xmbm
{
...
} [AuditLogAttribute("姓名")]
public string Name
{
...
} [AuditLogAttribute("单位")
public UnitInfoBase Unit
{
...
}
}

在修饰属性Property时,如果该属性是复杂类型(类),比如上例子中的Unit,那么此类必须满足下面的两个要求:

  1. 类必须重载ToString()方法,以便在数据库中能够正确的保存其值;
  2. 类必须重载Equal(object )方法,用于比较修改前后值,以判断是否该实例的值是否需要记录到日志中;

4、注入DAO:如何在每一个实体类的Dao中注入保存日志的方法是关键点,由于项目中使用NHibernate来实现dao,这就给实现增加了很多便利,NHibernate有接口IInterceptor,可以直接注入代码

在NHibernateSessionModule模块中开始事务之前增加,注入Interceptor的代码:

public void Init(HttpApplication context) {
context.BeginRequest += new EventHandler(BeginTransaction);
context.EndRequest += new EventHandler(CommitAndCloseSession);
} /// Opens a session within a transaction at the beginning of the HTTP request.
/// This doesn't actually open a connection to the database until needed.
private void BeginTransaction(object sender, EventArgs e) { //增加自己的注入代码,InterceptorWithAuditLog类的实例
NHibernateSessionManager.Instance.RegisterInterceptor(new InterceptorWithAuditLog());
NHibernateSessionManager.Instance.BeginTransaction();
//NHibernateSessionManager.Instance.GetSession().FlushMode = NHibernate.FlushMode.Never;
} /// Commits and closes the NHibernate session provided by the supplied
/// Assumes a transaction was begun at the beginning of the request; but a transaction or session does
/// not *have* to be opened for this to operate successfully.
private void CommitAndCloseSession(object sender, EventArgs e) {
try {
NHibernateSessionManager.Instance.CommitTransaction();
}
finally {
NHibernateSessionManager.Instance.CloseSession();
}
} public void Dispose() { }
}

5、web配置

<sectionGroup name="system.web">
<section name="auditLogService" type="OilDigital.Common.Log.AuditLogServiceSection, Common" allowDefinition="MachineToApplication" restartOnExternalChanges="true"/>
</sectionGroup> ... <system.web>
<auditLogService defaultProvider="sqlAuditLogProvider">
<providers>
<add name="sqlAuditLogProvider" type="OilDigital.Common.Log.SQLAuditLogProvider, Common" connectionStringName="LocalSqlServer" />
</providers>
</auditLogService>
</system.web>

主要配置保存修改日志的Provider,在Common.dll中提供了一个缺省的保存日志Provider:SQLAuditLogProvider,该类实现了AuditLogProvider接口(抽象类),把日志WebAuditLog实例保存到数据库中.使用该类必须配置其connectionStringName属性为数据库连接串的名字,此外他还提供了 另一个配置选项enabled,如果enabled="false",那么表明此保存控件被跳过. 主要配置保存修改日志的Provider,在Common.dll中提供了一个缺省的保存日志Provider:SQLAuditLogProvider,该类实现了AuditLogProvider接口(抽象类),把日志WebAuditLog实例保存到数据库中.使用该类必须配置其connectionStringName属性为数据库连接串的名字,此外他还提供了另一个配置选项enabled,如果enabled="false",那么表明此Provider处于disabled状态,不执行任何操作。

6、如果使用SQLAuditLogProvider,那么必须在数据库中添加表AuditLog:

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[AuditLog]') and OBJECTPROPERTY(id, N'IsUserTable') = )
drop table [dbo].[AuditLog]
GO CREATE TABLE [dbo].[AuditLog] (
[id] [bigint] IDENTITY (, ) NOT NULL ,
[userid] [varchar] () COLLATE Chinese_PRC_CI_AS NULL ,
[ip] [varchar] () COLLATE Chinese_PRC_CI_AS NULL ,
[actiontype] [nvarchar] () COLLATE Chinese_PRC_CI_AS NULL ,
[classname] [nvarchar] () COLLATE Chinese_PRC_CI_AS NULL ,
[propertyName] [nvarchar] () COLLATE Chinese_PRC_CI_AS NULL ,
[originalvalue] [nvarchar] () COLLATE Chinese_PRC_CI_AS NULL ,
[currentvalue] [nvarchar] () COLLATE Chinese_PRC_CI_AS NULL ,
[entityId] [varchar] () COLLATE Chinese_PRC_CI_AS NULL ,
[updatetime] [datetime] NULL ,
[ThreadId] bigint not null default
) ON [PRIMARY]
GO ALTER TABLE [dbo].[AuditLog] WITH NOCHECK ADD
CONSTRAINT [PK_AuditLog] PRIMARY KEY CLUSTERED
(
[id]
) ON [PRIMARY]
GO

7、创建存储过程

CREATE procedure usp_auditlog_insert
(
@userid varchar(),
@ip varchar(),
@actiontype varchar(),
@classname nvarchar(),
@entityid varchar(),
@propertyname nvarchar(),
@originalvalue nvarchar(),
@currentvalue nvarchar(),
@updatetime datetime,
@threadid bigint
)
as
begin
insert into auditlog
(userid
,ip,
actiontype,
classname,
entityid,
propertyname,
originalvalue,
currentvalue,
updatetime,
threadid
)
values
(
@userid,
@ip,
@actiontype,
@classname,
@entityid,
@propertyname,
@originalvalue,
@currentvalue,
@updatetime,
@threadid
) end
GO

8、重点是 InterceptorWithAuditLog()  这个类的代码如何写呢?

C# 定制特性的更多相关文章

  1. C# mvc中为Controller或Action添加定制特性实现登录验证

    在本文开始前,先简单讲两个知识点: 1.每个action执行前都会先执行OnActionExecuting方法: 2.FCL提供了多种方式来检测特性的存在,比如IsDefined.GetCustomA ...

  2. <NET CLR via c# 第4版>笔记 第18章 定制特性

    18.1 使用定制特性 FCL 中的几个常用定制特性. DllImport 特性应用于方法,告诉 CLR 该方法的实现位于指定 DLL 的非托管代码中. Serializable 特性应用于类型,告诉 ...

  3. 重温CLR(十三) 定制特性

    利用定制特性,可宣告式为自己的代码构造添加注解来实现特殊功能.定制特性允许为几乎每一个元数据表记录项定义和应用信息.这种可扩展的元数据信息能在运行时查询,从而动态改变代码的执行方式.使用各种.NET技 ...

  4. 编写高质量代码改善C#程序的157个建议——建议55:利用定制特性减少可序列化的字段

    建议55:利用定制特性减少可序列化的字段 特性(attribute)可以声明式地为代码中的目标元素添加注释.运行时可以通过查询这些托管块中的元数据信息,达到改变目标元素运行时行为的目的.System. ...

  5. C#中的定制特性(Attributes)

    C#中的定制特性(Attributes) 介绍 Attributes是一种新的描述信息,我们既可以使用attributes来定义设计期信息(例如:帮助文件.文档的URL),还可能用attributes ...

  6. clr via c# 定制特性

    1,特性的应用范围:特性可应用于程序集,模块,类型,字段,方法,方法参数,方法返回值,属性,参数,泛型参数 2,利用前缀告诉编译器表明意图---下面的倾斜是必须的表明了我们的目标元素: [assemb ...

  7. asp.net Core 2.0 MVC为Controller或Action添加定制特性实现登录验证

    前言:最近在倒腾 微软的新平台 asp.net Core 2.0,在这个过程中有些东西还是存在差异.下面是我在学习过程的一点笔记.有不妥之处,望各位大虾指正! 一.先创建一个控制器继承于Control ...

  8. 学会给你的类(及成员)来定制一套自己的Attribute吧

    在通过Visual Studio创建的C#程序集中,都包含了一个AssemblyInfo.cs的文件,在这个文件中,我们常常会看到这样的代码 [assembly: AssemblyTitle(&quo ...

  9. 【C#进阶系列】18 特性Attribute

    这个东西有的叫定制特性,然而我喜欢直接叫特性,但是这样的话一些人不知道我说的是什么,如果我说是Attribute的话那么知道的或者用过的就都懂了. 还记得讲到枚举和位标志那一章,关于位标志,有一个[F ...

随机推荐

  1. 20165219 2017-2018-2 《Java程序设计》第8周学习总结

    20165219 2017-2018-2 <Java程序设计>第8周学习总结 教材学习内容总结 进程与线程 线程是比进程更小的单位:线程间可以共享进程中的某些内存单元 java的多线机制 ...

  2. jwt-dotnet使用示例

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  3. 最短路径 Dijkstra算法 AND Floyd算法

    无权单源最短路:直接广搜 void Unweighted ( vertex s) { queue <int> Q; Q.push( S ); while( !Q.empty() ) { V ...

  4. ubuntu 软件使用

    1.制作iso: mkisofs -r -o file.iso your_folder_name/

  5. 关于执行webdriver.Chrome; 报错WebDriverException: Message: unknown error: Element is not clickable at point (1085, 103)

    from selenium import webdriverfrom time import sleep dr = webdriver.Chrome() dr.get("http://pj1 ...

  6. 【转】Cannot add or update a child row: a foreign key constraint fails 解决办法

    原因:设置的外键和对应的另一个表的主键值不匹配.解决方法:找出不匹配的值修改.或者清空两表数据. 转自https://blog.csdn.net/qq_29405421/article/details ...

  7. Spring学习笔记(三)—— 使用注解配置spring

    一.使用步骤 1.1 导包 1.2 为主配置文件引入新的命名空间(约束) 在applicationContext.xml中引入context约束 1.3 编写相关的类 public class Use ...

  8. Hibernate学习笔记(三)—— Hibernate的事务控制

    Hibernate是对JDBC的轻量级封装,其主要功能是操作数据库.在操作数据库过程中,经常会遇到事务处理的问题,接下来就来介绍Hibernate中的事务管理. 在学习Hibernate中的事务处理之 ...

  9. QQ在线状态的使用

    在网页中显示QQ在线状态并点击后发起对话,是很多门户网站常见的一个功能,这两天就碰到这样一个.原以为很简单,结果还是折腾了半天,虽然是个小问题,但也值得记录一下. 按以前的经验,网上有很多QQ在线代码 ...

  10. POJ2449 Remmarguts' Date A*算法

    题意是让求从st的ed第k短路... 考虑A*算法:先把终点到每个点最短路跑出来(注意要建反图),当做估价函数h(u),然后跑A* 每次取出总代价最小的,即g(u)+h(u)最小的进行扩展,注意如果u ...