.NET编译器的任务之一就是为所有定义和引用的类型生成元数据描述。除了程序集中标准的元数据外,.NET平台还支持特定(attribute)把更多的元数据嵌入到程序集中。

.NET特性扩展了抽象的System.Attribute基类,.NET中有很多预定义的特性,例如:[DllImport]、[Obsolete]和[Serializable]等等。

看一个Obsolete使用的例子,Obsolete特性用来标记不用的类或成员,当其他程序试图访问该项的时候,将会得到一个警告或错误描述。

static class StringUtil
{
public static string ReverseString(string str)
{
Console.WriteLine("call reverseString");
return "";
} [Obsolete("this legacy method should not be used", true)]
public static string LegacyReverseString(string str)
{
Console.WriteLine("call legacyReverseString");
return "";
}
} class Program
{
static void Main(string[] args)
{
string str = StringUtil.LegacyReverseString("Hello World!");
Console.Read();
}
}

使用上面的代码,我们就会的到一个错误消息,提示我们不应该在使用这个方法了

当然,我们也可以通过代码来添加自定义特性,在开始自定义特性之前,我们需要知道以下概念。

自定义特性

在代码中,我们可以创建自定义的特性类型,但是这个类型一定要直接或间接从System.Attribute派生。下面我们就定义了一个TableAttribute特性:

[AttributeUsage(AttributeTargets.Class, Inherited=false, AllowMultiple=false)]
public class TableAttribute : Attribute
{
public TableAttribute()
{
} public TableAttribute(string tableName)
{
this.TableName = tableName;
} public string TableName { get; set; }
}

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

例如:当我们在代码中使用的时候,特性表现为[Obsolete],但是实际上类型是ObsoleteAttribute,而不是代码中的Obsolete。当.NET Framework进行名称转换的时候,所有的.NET特性(包括自定义特性)都将加上一个Attribute标记的后缀。

AttributeUsage

在上面的自定义特性中,有下面一行代码,我们给自定义特性应用了一个AttributeUsage特性。

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

AttributeUsage类是一个预定义特性类,通过这个特性,我们可以控制自定义特性的使用,它描述了一个定制特性如和被使用。

[Serializable]
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
[ComVisible(true)]
public sealed class AttributeUsageAttribute : Attribute
{
public AttributeUsageAttribute(AttributeTargets validOn); public bool AllowMultiple { get; set; } public bool Inherited { get; set; } public AttributeTargets ValidOn { get; }
}

通过代码可以看到,AttributeUsage有三个属性:

  • ValidOn

    从代码中可以看到ValidOn的类型为System.AttributeTargets, 而AttributeTargets本身是

    一个枚举,这样就可以通过按位"或"运算组合 AttributeTargets,从而指示哪些程序元素是有效的。

    例如:

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

  • AllowMultiple

    这个属性标记了特性能否被重复放置在同一个程序实体前多次。

  • Inherited

    这个属性来控制定制特性的继承规则,表示特性能否被继承。

从上面的介绍可以看到,创建自定义特性的大概步骤:

  1. 声明特性类,由 System.Attribute 直接或间接派生
  2. 使用AttributeUsage特性来控制自定义特性
  3. 声明特性类构造函数

特性和反射结合

当我们使用特性的时候,只是给程序集添加了一些元数据。当结合反射使用的时候,特性就能发挥很大的作用了。

下面看一个特性和反射结合的例子,在例子中自定义了Table和Column特性,然后把这些特性应用到了我们的User类型上面;然后结合一个自定义的ORM类型,将对象的Inster()操作转换成SQL语句。

namespace AttributeTest
{
[AttributeUsage(AttributeTargets.Class, Inherited=false, AllowMultiple=false)]
public class TableAttribute : Attribute
{
public TableAttribute()
{
} public TableAttribute(string tableName)
{
this.TableName = tableName;
} public string TableName { get; set; }
} [AttributeUsage(AttributeTargets.Property, Inherited=false, AllowMultiple=false)]
public class ColumnAttribute:Attribute
{
public ColumnAttribute()
{
} public ColumnAttribute(string columnName)
{
this.ColumnName = columnName;
} public ColumnAttribute(string colunmName, DbType dbType)
{
this.ColumnName = colunmName;
this.DbType = dbType;
} public string ColumnName { get; set; }
public DbType DbType { get; set; }
} public class CustomizeORM
{
public void Insert(object table)
{
Type type = table.GetType();
Dictionary<string, string> columnValueDict = new Dictionary<string, string>();
StringBuilder SqlStr = new StringBuilder();
SqlStr.Append("insert into ");
TableAttribute tableAttribute = (TableAttribute)type.GetCustomAttributes(typeof(TableAttribute), false).First();
SqlStr.Append(tableAttribute.TableName);
SqlStr.Append("("); foreach (var prop in type.GetProperties())
{
foreach (var attr in prop.GetCustomAttributes())
{
string value = prop.GetValue(table).ToString();
ColumnAttribute columnAttribute = attr as ColumnAttribute;
if (columnAttribute != null)
{
columnValueDict.Add(columnAttribute.ColumnName, value);
}
}
} foreach (var item in columnValueDict)
{
SqlStr.Append(item.Key);
SqlStr.Append(","); }
SqlStr.Remove(SqlStr.Length - , );
SqlStr.Append(") values('");
foreach (var item in columnValueDict)
{
SqlStr.Append(item.Value);
SqlStr.Append("','");
}
SqlStr.Remove(SqlStr.Length - , );
SqlStr.Append(")");
Console.WriteLine(SqlStr.ToString());
}
} [Table("Users")]
public class User
{
[Column(ColumnName="Id",DbType=DbType.Int32)]
public int UserID{get;set;}
[Column(ColumnName="Name",DbType=DbType.String)]
public string UserName { get; set; }
[Column(ColumnName = "Age", DbType = DbType.Int32)]
public int Age { get; set; }
} class Program
{
static void Main(string[] args)
{
CustomizeORM customizeORM = new CustomizeORM();
User user = new User() { UserID = , UserName = "Wilber", Age = };
customizeORM.Insert(user); Console.Read();
}
} }

代码的执行结果为:

从这个例子中可以看到,通过特性给程序集加入的元数据,可以在运行时被反射程序得到并使用。

通过IL代码可以看到特性的简化,代码中我们使用Table、Column对User类型进行修饰,在IL代码中都加上了Attribute后缀;同时还可以看到,这些特性都变成了User类型的元数据。

总结

通过本文介绍了.NET特性,同时介绍了自定义特性需要的基本知识。

特性本身只是给程序集添加一些元数据,当结合反射使用的时候,这些被添加的元数据才能发挥更大的作用。

反射简介—C#特性和反射的更多相关文章

  1. Day07 jdk5.0新特性&Junit&反射

    day07总结 今日内容 MyEclipse安装与使用 JUnit使用 泛型 1.5新特性 自动装箱拆箱 增强for 静态导入 可变参数方法 枚举 反射 MyEclipse安装与使用(yes) 安装M ...

  2. Java 反射(一)反射简介、原理和应用场景

    目录 一.动态语言和动态语言的比较 动态语言 静态语言 二.反射 简介 反射的常见使用 1. 代码编辑器 2. Spring等框架的IoC容器 3. 和注解的配合使用 原理 反射优缺点 调试查看 Cl ...

  3. C#特性和反射

    C#特性和反射 .NET编译器的任务之一就是为所有定义和引用的类型生成元数据描述.除了程序集中标准的元数据外,.NET平台还支持特定(attribute)把更多的元数据嵌入到程序集中. .NET特性扩 ...

  4. 利用特性和反射给泛型Model赋值

    为了解决从数据库读取的表字段和自己建的viewModel字段名称不相符的问题 本人小白,初次将特性及反射应用到实例,写的不好的地方还请大家多多包涵 新建一个控制台应用程序命名为ReflectAndAt ...

  5. EF+LINQ事物处理 C# 使用NLog记录日志入门操作 ASP.NET MVC多语言 仿微软网站效果(转) 详解C#特性和反射(一) c# API接受图片文件以Base64格式上传图片 .NET读取json数据并绑定到对象

    EF+LINQ事物处理   在使用EF的情况下,怎么进行事务的处理,来减少数据操作时的失误,比如重复插入数据等等这些问题,这都是经常会遇到的一些问题 但是如果是我有多个站点,然后存在同类型的角色去操作 ...

  6. 详解C#特性和反射(一)

    使用特性(Attribute)可以将描述程序集的信息和描述程序集中任何类型和成员的信息添加到程序集的元数据和IL代码中,程序可以在运行时通过反射获取到这些信息: 一.通过直接或间接的继承自抽象类Sys ...

  7. 详解C#特性和反射(四)

    本篇内容是特性和反射的最后一篇内容,前面三篇文章: 详解C#特性和反射(一) 详解C#特性和反射(二) 详解C#特性和反射(三) 一.晚期绑定(Late Binding)是一种在编译时不知道类型及其成 ...

  8. day27<反射&JDK5新特性>

    反射(类的加载概述和加载时机) 反射(类加载器的概述和分类) 反射(反射概述) 反射(Class.forName()读取配置文件举例) 反射(通过反射获取带参构造方法并使用) 反射(通过反射获取成员变 ...

  9. 详解C#泛型(二) 获取C#中方法的执行时间及其代码注入 详解C#泛型(一) 详解C#委托和事件(二) 详解C#特性和反射(四) 记一次.net core调用SOAP接口遇到的问题 C# WebRequest.Create 锚点“#”字符问题 根据内容来产生一个二维码

    详解C#泛型(二)   一.自定义泛型方法(Generic Method),将类型参数用作参数列表或返回值的类型: void MyFunc<T>() //声明具有一个类型参数的泛型方法 { ...

随机推荐

  1. Spring Boot 上传文件 获取项目根路径 物理地址 resttemplate上传文件

    springboot部署之后无法获取项目目录的问题: 之前看到网上有提问在开发一个springboot的项目时,在项目部署的时候遇到一个问题:就是我将项目导出为jar包,然后用java -jar 运行 ...

  2. 解决华为手机图片选择无效及产生的open failed: EACCES (Permission denied)错误

    在华为手机上调起图片选择时原来的效果如下 原来的代码是 Intent intent = new Intent(); intent.setAction(Intent.ACTION_GET_CONTENT ...

  3. 【android】环形进度条实现

    先上效果图(压缩尺寸后出现锯齿,原图边缘很细腻的喂~) 特性: 1:支持环形带字 .环形不带字(中间盖上圆形图片,实现天天动听播放器在通知栏播放进度的效果).实心 2:线程安全,不需要写handler ...

  4. QTcpSocket的连续发送数据和连续接收数据

    关于这个问题折腾了我好久,以前做些小练习的时候,用QTcpSocket的write()一数据,然后接收方只要emit一个readyread()信号然后就用QTcpSocket的read()去读.本以为 ...

  5. UPNP

    基本概念 UPnP 的应用范围非常大,以致足够可以实现许多现成的.新的及令人兴奋的方案,包括家庭自动化.打印.图片处理.音频 / 视频娱乐.厨房设备.汽车网络和公共集会场所的类似网络.它可以充分发挥 ...

  6. SAP FI 财务模块 关键用户 考试练习 问卷

    FI概念部分课后练习:(20题, 开卷,本周五内完毕) PC端自我测试: http://www.xiaocar.net/index.php?s=/addon/Exam/Exam/show/exam_i ...

  7. 第三百七十八节,Django+Xadmin打造上线标准的在线教育平台—django自带的admin后台管理介绍

    第三百七十八节,Django+Xadmin打造上线标准的在线教育平台—django自带的admin后台管理介绍 配置django的admin数据库管理后台 首先urls.py配置数据库后台路由映射,一 ...

  8. ibatis中 $ 于 # 的 区别?

    转自: http://www.blogjava.net/lsbwahaha/archive/2009/04/16/266026.html 一个项目中在写ibatis中的sql语句时,where use ...

  9. 开源项目推荐:e-example / Springboot+bootstrap + ……

    前言: 我想要找一个 springboot + bootstrap 的例子介绍,然后搜索到了这个开源项目. 所有能跑起来的项目都有研究价值,看看这个项目的文档.目前正好满足我想要的功能.推荐 正文: ...

  10. 安装drools workbench

    从drools官网下载tomcat7版本的Drools  Tomcat 7+ WAR, Workbench,实际就是一个war包,需要严格按照里面的readme的要求,配置好tomcat才可以运行起来 ...