.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. Map总结(HashMap, Hashtable, TreeMap, WeakHashMap等使用场景)

    概要 学完了Map的全部内容,我们再回头开开Map的框架图. 本章内容包括:第1部分 Map概括第2部分 HashMap和Hashtable异同第3部分 HashMap和WeakHashMap异同 转 ...

  2. VS2008中设置字体大小和添加显示行号

    1.字体设置 菜单->工具->选项->环境->字体和颜色 在字体那一栏可以选字体,VC6.0默认的字体是Fixedsys,字体大小你可以设为12,就与VC6.0的一样的字体了. ...

  3. 纯css控制文字2行显示多余部分隐藏

    在编写页面的时候,经常遇到一些地方的文字显示1行,多余的文字隐藏,这样显示1行的很好控制: css代码如下: white-space: nowrap; overflow: hidden; text-o ...

  4. 【转】PyCharm LicenseServer 破解

    在自己的云平台上部署了IntelliJIDEALicenseServer服务,理论上http://www.jetbrains.com的产品都可以使用,不过目前只测试了PyCharm. 大家也知道,Py ...

  5. linux进程的挂起和恢复

    进程的挂起及恢复 #ctrl+z:挂起,程序放到后台,程序没有结束. #jobs:查看被挂起的程序工作号 恢复进程执行时,有两种选择:fg命令将挂起的作业放回到前台执行:用bg命令将挂起的作业放到后台 ...

  6. Flume exec 测试

    环境:ubuntu 1604 软件:①apache-flume-1.7.0-bin.tar.gz,解压后放到 /usr/local/  下面.②sudo apt-get install apache2 ...

  7. 记一些常用到的python中的函数

    1. zip()函数 它的作用是从参数中按顺序一一抽出子参数组出一个新的tuple.  直接看例子: >>> mean = np.array([2, 5, 4]) >>& ...

  8. python3 pyodbc简单使用

    转自:https://my.oschina.net/zhengyijie/blog/35587 1.连接数据库 首先要import pyodbc 1)直接连接数据库和创建一个游标(cursor) cn ...

  9. (转)在SDL工程中让SDL_ttf渲染汉字

    有时候在关于SDL的博文中看到一些评论,说SDL对中文的支持不佳,因为当程序涉及中文时总是输出乱码. 照我个人观点,这里面很多都是误解.下面就根据我在windows下使用SDL的情况,说说我的观点. ...

  10. svn -- svn数据仓库

    在svn中我们的项目,不能称之为项目或文件夹,而是称之为“仓库” 仓库的建立步骤: 1.创建代码仓库 l 在任意盘符下建立文件夹(D:\svn\myApp\)做为我们的版本库根目录,如我们需要建立一个 ...