- 自定义特性的应用:
自定义特性是一个特殊的类,它必须遵循两个规范:这个特性类必须派生自System.Attribute;这个类的构造函数只能包含可在编译时解析的类型,如字符串类型或整数类型。
1、在单元测试软件中,常常需要定义一系列的测试类来检查类,在回归测试中也是如此,希望确保更正一个错误而不必破坏其他的代码。这样就需要提供代码和测试类之间的交叉引用,下面的自定义特性可以帮助跟踪类及其测试类。
示例:
using System;
using System.Reflection;
namespace UnitTesting
{ //////////////////////////////自定义特性类//////////////////////////////////////
[AttributeUsage(AttributeTargets.Class,AllowMultiple=false,Inherited=true)]
public class TestCaseAttribute:Attribute
{
public readonly System.Type TestCase;//只读成员,用于测试给定类的对象类型,所以使用System.Type类型成员
public TestCaseAttribute(System.Type testCase) //构造函数
{TestCase=testCase;}
public void Test()
{
object obj=Activator.CreateInstance(TestCase); //创建TestCase实例
}
}
///////////////////////////////测试一个对象TestAnObject///////////////////////
[TestCase(typeof(TestAnObject))]
public class SomeCode
{
public SomeCode()
{}
public int Do()
{return 999;}
}
////////////////////////////////////测试类////////////////////////////////////////
public class TestAnObject
{
public TestAnObject()
{
SomeCode scode=new SomeCode();
if(scode.Do()!=999)
{
throw new Exception("错误");
}
}
}
///////////////////////////////主程序代码////////////////////////////////////////
public class UnitTest
{
public static void Main()
{
Assembly asm=Assembly.GetExecutingAssembly();//获取当前代码所在的组件
System.Type[] types=asm.GetExportedTypes();//获取组件asm中定义的导出类型,将找到类TestCaseAttribute
foreach(System.Type type in types)
{
Console.WriteLine("Checking type {0}",type.ToString());//打印出测试对象的类型,这里将打印出类型SomeCode类型
//获取SomeCode类中由System.Type标识别的所有的自定义的特性,并放在数组里
object[] atts=type.GetCustomAttributes(typeof(TestCaseAttribute),false);
if(1==atts.Length)//检查是否有自定义特性在数组里
{
Console.WriteLine("找到TestCaseAttribute特性,开始测试...");
TestCaseAttribute tca=atts[0] as TestCaseAttribute;
try
{
tca.Test();//创建测试用例类型的实例,如果成功,则测试通过
Console.WriteLine("测试通过");
}
catch(Exception ex)
{
Console.WriteLine("测试没通过");
Console.WriteLine(ex.ToString());
}
}
}
}
}
}
示例代码补充解释:
自定义特性TestCase通过代码行[AttributeUsage(AttributeTargets.Class,AllowMultiple=false,Inherited=true)]
说明此类要使用系统内置特性:AttributeUsage
AttributeTargets.Class是系统特性AttributeUsage的构造函数的第一个参数,指明自定义特性的作用域,这里限定自定义特性使用在类上,AttributeTargets是一个枚举,其他可以选择的值有:
All(特性可以在组件的任何地方有效)、Assembly(特性在组件上有效)、Constructor(特性在类的构造函数上有效)、Delegate(特性在委托中有效)、Event(特性在事件定义上有效)、Field(特性可以放在内部成员上)、InterFace(特性在接口上有效)、Method(特性在方法上有效)。使用多个枚举值时,使用或”|”连接。
AllowMultiple=false不能给元素添加一个以上相同的特性。
Inherited=true标志自定义的特性是可以继承的。
★ 上面的代码通过创建自定义特性来交叉引用测试类和代码类,其中用到了系统特性[AttributeUsage]
下面将使用自定义的特性在.NET类中生成数据库模式(包括表、列和数据类型)
2、如果数据库变化不是很频繁,使用自定义特性可以提供一种高效的访问数据库的方式。
示例:
using System;
using System.Data;
using System.Collections;
using System.Reflection;
namespace DBAttribute
{
//////////////////////////////////////自定义特性类的定义:此特性应用于class,不可继承此类,不可重复在一个类上使用多个此属性//////////////////////////////////////////
[AttributeUsage(AttributeTargets.Class,Inherited=false,AllowMultiple=false)]
public class DatabaseTableAttribute:Attribute
{
public readonly string TableName;//公共成员,设置table名称
//构造函数,在使用此自定义特性时,需要传入表名称
public DatabaseTableAttribute(string tableName) {TableName=tableName;}
}
/////////////////////////自定义特性类的定义:此特性应用于Property,可继承此类,不可重复在一个属性上使用多个此属性////////////////////////////////////////////////////////////
[AttributeUsage(AttributeTargets.Property,Inherited=true,AllowMultiple=false)]
public class DatabaseColumnAttribute:Attribute
{
public readonly string ColumnName;
public readonly ColumnDomain DataType;
public bool Nullable=false;
public int Order;//确定列的顺序,生成表时,列以升序输出
public int Size;
private static int nextOrder=100;
public DatabaseColumnAttribute(string column,ColumnDomain dataType)
{
ColumnName=column;
DataType=dataType;
Order=GeneraterOrderNumber();//确定列以升序输出
}
public static int GeneraterOrderNumber(){return nextOrder++;}
}
//////////////////////////////定义一个抽象类,继承自DataRow类,其他子类可以从此抽象类继承,对于有几个表中有相同字段的情况比较使用///////////////////////////////////////////////////
public abstract class GenericRow:DataRow
{
public GenericRow(DataRowBuilder builder)
:base(builder){}//调用基类的构造函数
//使用自定义的特性创建列,将调用DatabaseColumnAttribute类的构造函数
[DatabaseColumn("NAME",ColumnDomain.String,Order=10,Size=64)]
public string Name
{
get{return (string)this["NAME"];}
set{this["NAME"]=value;}
}
[DatabaseColumn("DESCRIPTION",ColumnDomain.String,Order=11,Size=1000)]
public string Description
{
get{return (string)this["DESCRIPTION"];}
set{this["DESCRIPTION"]=value;}
}
}
//////////////////////////////////////////////使用自定义的特性创建表AUTHOR,将调用DatabaseTableAttribute类的构造函数////////////////////////////////////////////////
[DatabaseTable("AUTHOR")]
public class AuthorRow:GenericRow//继承自抽象类GenericRow
{
public AuthorRow(DataRowBuilder builder)
:base(builder) {}
[DatabaseColumn("AUTHOR_ID",ColumnDomain.Long,Order=1)] //Order=1,AUTHOR_ID是第一列
public long AuthorID
{
get{return (long)this["AUTHOR_ID"];}
set{this["AUTHOR_ID"]=value;}
}
[DatabaseColumn("HIRE_DATE",ColumnDomain.DateTime,Nullable=true)]
public DateTime HireDate
{
get{return (DateTime)this["HIRE_DATE"];}
set{this["HIRE_DATE"]=value;}
}
}
///////////生成SQL,将生成的SQL语句打印在控制台,实际使用时可以不这样打印//////
public class DatabaseTest
{
public static void Main()
{
OutPutTable(typeof(AuthorRow));
}
public static void OutPutTable(System.Type type)
{
object[] tableAttributes=type.GetCustomAttributes(typeof(DatabaseTableAttribute),true);
if(tableAttributes.Length ==1)
{
Console.WriteLine("CREATE TABLE {0}",((DatabaseTableAttribute)tableAttributes[0]).TableName);
Console.WriteLine("(");
SortedList columns=new SortedList();
foreach(PropertyInfo prop in type.GetProperties())
{
object[] columnAttributes=prop.GetCustomAttributes(typeof(DatabaseColumnAttribute),true);
if(columnAttributes.Length ==1)
{
DatabaseColumnAttribute dca=columnAttributes[0] as DatabaseColumnAttribute;
string dataType=ConvertDataType(dca);
columns.Add(dca.Order,string.Format(" {0,-31}{1,-20}{2,8},",dca.ColumnName,dataType,dca.Nullable?"NULL":"NOT NULL"));//格式化输出
}
foreach(DictionaryEntry e in columns)//重新排序后输出
{
Console.WriteLine(e.Value);
Console.WriteLine(")");
Console.WriteLine("GO");
}
}
}
}
//Main函数里调用的方法
private static string ConvertDataType(DatabaseColumnAttribute dca)
{
string dataType=null;
switch(dca.DataType)//依据枚举将类型转化为数据库特定的类型
{
case ColumnDomain.DateTime:
{
dataType="DATETIME";break;
}
case ColumnDomain.Integer:
{
dataType="INT";break;
}
case ColumnDomain.Long:
{
dataType="BIGINT"; break;
}
case ColumnDomain.String:
{
dataType=string.Format("VARCHAR({0})",dca.Size);break;
}
}
return dataType;
}
}
//枚举定义
public enum ColumnDomain
{
Integer,
Long,
String,
DateTime
}
}
说明:上例中多斜线注释(如:“////////“)都是类的的注释