.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. windows下安装和配置多个版本的JDK

    https://jingyan.baidu.com/article/47a29f2474ba55c015239957.html 如何在windows下安装和配置多个版本的jdk,本文将带你在windo ...

  2. SimpleDraweeView 设置圆角不生效问题

    采用的是xml配置 roundedCornerRadius 参数方法 adverPic.setImageURI("http://xx.xx.xx.xx/123.jpg"); 设置c ...

  3. RMySQL数据库编程指南

    R语言作为统计学一门语言,一直在小众领域闪耀着光芒.直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器.随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长.现在已不仅仅是统计领域,教 ...

  4. Sword STL容器分类介绍

    标准STL序列容器:vector.string.deque和list. 标准STL关联容器:set.multiset.map和multimap. 非标准序列容器slist和rope.slist是一个单 ...

  5. Q_UNUSED() 方法的使用

    Q_UNUSED() 没有实质性的作用,用来避免编译器警告 //比如说 int testFunc(int a, int b, int c, int d) { int e; return a+b+c; ...

  6. DPI (深度报文检测) 关于DPI的学习笔记

    关于DPI的学习笔记 先看一下定义 : DPI(Deep Packet Inspection)是一种基于数据包的深度检测技术,针对不同的网络应用层载荷(例如HTTP.DNS等)进行深度检测,通过对报文 ...

  7. listview item 动画

    http://blog.csdn.net/lixiaodaoaaa/article/details/8284246 先看实现的效果如下: 演示效果如上:   上面的列表是GridView 要给这个Gr ...

  8. Android WiFi 日志记录(四次握手)

    记录一下四次握手的log. PMK: PMK(Pairwise Master Key,成对主密钥 STA和AP得到PMK后,将进行密匙派生以得到PTK.最后,PTK被设置到硬件中, 用于数据的加解密. ...

  9. VIM的自动补全

    自动补全可以很好的提高输入的效率: 触发自动补全的方法总结: 可供弹出式菜单的使用的命令: 其它再补充:

  10. vi 新建文件后保存文件时遇到的问题:E212: 无法打开并写入文件

    问题描述 使用vi编辑器写好内容后保存并退出时遇到以下问题 解决方案 该问题的原因是用户权限不够,因为普通用户用 vi 不能保存文件,需要使用超级用户才可以. 先转换为超级用户:su 再用vi打开文件 ...