.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. 2013Hulu校园招聘笔试题

    填空题:1.中序遍历二叉树,结果为ABCDEFGH,后序遍历结果为ABEDCHGF,先序遍历结果为?  FCBADEGH  如下图所示: 2.对字符串HELL0_HULU中的字符进行二进制编码,使得字 ...

  2. java BlockingQueue 用法

    在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题.通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利.本文 ...

  3. R matrix 转换为 dataframe

    When I try converting a matrix to a data frame, it works for me: > x <- matrix(1:6,ncol=2,dimn ...

  4. SparkR(R on Spark)编程指南 含 dataframe操作

    SparkR(R on Spark)编程指南 Spark  2015-06-09 28155  1评论 下载为PDF    为什么不允许复制 关注iteblog_hadoop公众号,并在这里评论区留言 ...

  5. Sword redis补充

    Redis 键(key) Redis 键命令用于管理 redis 的键. redis任何数据类型都有key --删除key的命令 redis> del key Redis 事务 Redis 事务 ...

  6. 安装GYP(Generate Your Projects)

    GYP(Generate Your Projects)是由 Chromium 团队开发的跨平台自动化项目构建工具,Chromium 便是通过 GYP 进行项目构建管理. 主页 :http://code ...

  7. MD2关键桢动画3D模型加载.

    在看Cg教程中,看到关键桢插值来表示一个动画的物体,例如一个动物拥有站着,奔跑,下跪等动画序列,美工将这些特定的姿态称为一个关键桢.为什么要用关键桢这种来表示了,这个比较容易理解,我们知道我们看的一些 ...

  8. python 查看目录下所有目录和文件

    python查看目录下所有的子目录和子文件 python递归遍历目录结构 我喜欢第一种 方法1 import json, os def list_dir(path, res): for i in os ...

  9. 理解Java动态代理(1)—找我还钱?我出钱要你的命

    代理模式是最常用的一个设计模式之一,理解起来也是很简单,一张图足以说明了,LZ就不废话了. 至于代理模式能干嘛也不是LZ今天想说的,今天主要想简单介绍下JAVA里面的动态代理.“动”当然是相对“静”来 ...

  10. C#网络唤醒

    什么是网络唤醒 网络唤醒实现了对网络的集中管理,即在任何时刻,网管中心的IT管理人员可以经由网络远程唤醒一台处于休眠或关机状态的计算机.使用这一功能,IT管理人员可以在下班后,网络流量最小以及企业的正 ...