阅读目录

前言

我们初学C#的时候看到类上面一对中括号里面有个高亮了的关键字,不知道那是什么有什么用。想问人又不知道它叫什么。纠结的要命。其实,它就是特性。如:

这就是我们今天要分析的主题。

特性是什么?

个人理解,特性就是修饰对象元数据的修饰符。

那么什么是“元数据”?

元数据就是用来描述数据的数据。(挺拗口的)

如:

图中的1.是特性 2.是访问修饰符 3.声明修饰符 4.数据类型 5.变量名 6.变量数据值,其中1、2、3、4、5就是元数据,用来描述数据(6)的数据。

特性到底是什么?

如上面的 Obsolete  ,会不会也是一个如 public  static 这样类似的修饰符呢,我们且看看反编译后的中间语言。

意料之外,我们看到了上面的2、3、4、5,而1(特性)怎么跑到里面去了,且是一种看不懂的东东,反正我们知道了不是类似的修饰符。

然后我们接着在vs里面把光标移到 Obsolete  上按F12,如:

原来只是一个继承了 Attrbute 的一个类(class)。那么上面我们看不懂的部分应该就是这个 ObsoleteAttribute 类的实例化了。

我们来回答上面问题:特性到底是什么?特性只是一个类而已。

我们自定义一个特性玩玩

我们看到上面系统特性 Obsolete 上面还有特性,如:Serializable、AttributeUsage、Camvisible等。像这种特性我们称之为“元数据的元数据”(元元数据)。

1.我们分别来解释性上面的三个特性。

Serializable:表示类型支持序列化。

ComVisible:微软定义“控制程序集中个别托管类型、 成员或所有类型对COM的可访问性”。

AttributeUsage:这个比较重要了,基本上每个特性定义都用到了它。它就是用来表示当前这个特性可用于哪些对象。如:类、方法、属性...等等。(只需要用到这个我们就可以自定义特性了)

2.上面有个问题,不知道大家发现没有。

就是我们特性名明明是 Obsolete  ,为什么我们F12进去后变成了 ObsoleteAttribute 呢?这其实只是一个微软的约定而已,没有为什么。

其实我们可以两种写法: [ObsoleteAttribute("已过时")] 和 [Obsolete("已过时")] 是等效的,只是我们一般都用后面这种。

3.定义的特性必须继承于 Attribute 。

4.属性没有set方法。只能通过构造函数赋值。(这是因为特性语法所致,因为特性的定义只存在单行的中括号中,不能实例化之后在设置属性,所以全部的设置都在后面的小括号里进行的。如果需要有set属性,我们就要用到命名参数,下面会继续讲到)

好了,我们通过这四点完全可以自己定义个特性来玩玩了。我们来定义一个给机器看的注释。我们平时的注释都只是给程序员看的,编译之后就全没了。那我们想在代码运行时,弹出我们的注释怎么办,接下来我们用自定义特性来实现,如:

[AttributeUsage(AttributeTargets.All)]//3.设置可用于哪些对象
public class TMessgAttribute : Attribute//1.定义类TMessg加上后缀TMessgAttribute 2.继承Attribute。
{
public TMessgAttribute() { } /// <param name="createTime">创建时间</param>
/// <param name="createName">创建人</param>
public TMessgAttribute(string createTime, string createName, string mess)
{
this._createName = createName;
this._createTime = createTime;
this._mess = mess;
} private string _createTime;
public string createTime
{
get { return _createTime; }//4.只能有get方法
}
private string _createName;
public string createName
{
get { return _createName; }
}
private string _mess;
public string mess { get { return _mess; } }
}

好了,上面就是我们自定义的特性。那我们怎样使用呢。和系统特性一样。我们先定义一个测试类TClass,然后在类上面定义特性,如:

[TMessg("2015-12-20", "zhaopei", "我只是测试自定义特性,不要报错哦,求求你了。")]
public class TClass
{
//................
}

我们定义了特性,也使用了特性,然我们却不知道怎么看效果。我们想看到效果怎么办。可以使用反射(下篇博问继续分析反射)看看 TClass 类的元数据,如:

static void Main(string[] args)
{
System.Reflection.MemberInfo info = typeof(TClass); //通过反射得到TClass类的信息
TMessgAttribute hobbyAttr = (TMessgAttribute)Attribute.GetCustomAttribute(info, typeof(TMessgAttribute));
Console.WriteLine("类名:{0}", info.Name);
Console.WriteLine("创建时间:{0}", hobbyAttr.createTime);
Console.WriteLine("创建人:{0}", hobbyAttr.createName);
Console.WriteLine("备注消息:{0}", hobbyAttr.mess);
Console.ReadKey();
}

打印效果如:

什么是命名参数?

上面的自定义特性都是通过构造函数设置字段私有字段,然后通过只提供了get的属性来访问。那么可否直接在特性里面定义拥有get和set的属性吗?答案是肯定的。那怎么在使用特性的时候设置这个属性呢?我们接着往下看。

我们接着在自定义特性里面添加一个属性。

/// <summary>
/// 修改时间
/// </summary>
public string modifyTime { get; set; }

使用自定义特性。

[TMessg("2015-12-20", "zhaopei", "我只是测试自定义特性,不要报错哦,求求你了。", modifyTime = "2015-12-21")]
public class TClass
{
//................
}

我们发现,直接在输入了构造函数之后接着设置属性就可以。(这就相当于可选参数了,属性当然可以随便你是否设置了。不过这里需要注意了,前面的参数一定要按照定义的特性构造函数的参数顺序)

这种参数,我们成为命名参数。

我们来继续要看看AttributeUsage(这个描述特性的特性--“元元数据”)

我们F12看看AttributeUsage的定义

看上去,同样也只是普通的特性。实际上也只是个普通的特性。>_<

我们来看看他的这几个属性是干嘛的。从最后一个开始看。

1.AttributeTargets,我们在上面其实就已经看到并也已经使用了。

我们设置的是可用于所有对象。AttributeTargets其实是个枚举,每个值对于一个类型对象。

你可以直接在 AttributeTargets F12进去:

 我们看到了每个值代表可以用于所对于的对象类型。

2.Inherited(是一个布尔值):“如果该属性可由派生类和重写成员继承,则为 true,否则为 false。 默认值为 true”

如下,我们设置 Inherited = false 那么继承TClass的T2Class无法访问到TClass中设置的特性元数据。

namespace net
{
[AttributeUsage(AttributeTargets.All, Inherited = false)]//3.设置可用于哪些对象
public class TMessgAttribute : Attribute//1.定义类TMessg加上后缀TMessgAttribute 2.继承Attribute。
{
public TMessgAttribute() { } /// <param name="createTime">创建时间</param>
/// <param name="createName">创建人</param>
public TMessgAttribute(string createTime, string createName, string mess)
{
this._createName = createName;
this._createTime = createTime;
this._mess = mess;
} private string _createTime;
public string createTime
{
get { return _createTime; }//4.只能有get方法
}
private string _createName;
public string createName
{
get { return _createName; }
}
private string _mess;
public string mess { get { return _mess; } }
/// <summary>
/// 修改时间
/// </summary>
public string modifyTime { get; set; }
} class Program
{
static void Main(string[] args)
{
System.Reflection.MemberInfo info = typeof(T2Class); //通过反射得到TClass类的信息
TMessgAttribute hobbyAttr = (TMessgAttribute)Attribute.GetCustomAttribute(info, typeof(TMessgAttribute));
Console.WriteLine("类名:{0}", info.Name);
if (hobbyAttr != null)
{
Console.WriteLine("创建时间:{0}", hobbyAttr.createTime);
Console.WriteLine("创建人:{0}", hobbyAttr.createName);
Console.WriteLine("备注消息:{0}", hobbyAttr.mess);
Console.WriteLine("修改时间:{0}", hobbyAttr.modifyTime); }
Console.ReadKey();
}
} [TMessg("2015-12-20", "zhaopei", "我只是测试自定义特性,不要报错哦,求求你了。", modifyTime = "2015-12-21")]
public class TClass
{
//................
} public class T2Class : TClass
{
//...........
}
}

反之,我们设置 Inherited = true (或者不设置任何,因为默认就是true)打印如下:

3.AllowMultiple(也是一个布尔值):“如果允许指定多个实例,则为 true;否则为 false。 默认值为 false。”

我们设置两个特性试试,如:

如果我们想要这样设置怎么办。在AttributeUsage中设置 AllowMultiple = true 如:

那么上面报错的地方将会打印:

注意:上面的打印地方的代码需要修改。因为之前是打印一个特性信息,这里是打印一个特性数组集合的信息。

static void Main(string[] args)
{
System.Reflection.MemberInfo info = typeof(T2Class);
TMessgAttribute[] hobbyAttr = (TMessgAttribute[])Attribute.GetCustomAttributes(info, typeof(TMessgAttribute));//修改1.这里需要取特性数据的集合了
Console.WriteLine("类名:{0}", info.Name);
for (int i = 0; i < hobbyAttr.Count(); i++)//修改2.这里需要循环打印了
{
Console.WriteLine("================================================");
Console.WriteLine("创建人:{0}", hobbyAttr[i].createName);
Console.WriteLine("创建时间:{0}", hobbyAttr[i].createTime);
Console.WriteLine("备注消息:{0}", hobbyAttr[i].mess);
Console.WriteLine("修改时间:{0}", hobbyAttr[i].modifyTime);
} Console.ReadKey();

全部代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks; namespace net
{
[AttributeUsage(AttributeTargets.All, Inherited = true, AllowMultiple = true)]//3.设置可用于哪些对象
public class TMessgAttribute : Attribute//1.定义类TMessg加上后缀TMessgAttribute 2.继承Attribute。
{
public TMessgAttribute() { } /// <param name="createTime">创建时间</param>
/// <param name="createName">创建人</param>
public TMessgAttribute(string createTime, string createName, string mess)
{
this._createName = createName;
this._createTime = createTime;
this._mess = mess;
} private string _createTime;
public string createTime
{
get { return _createTime; }//4.只能有get方法
}
private string _createName;
public string createName
{
get { return _createName; }
}
private string _mess;
public string mess { get { return _mess; } }
/// <summary>
/// 修改时间
/// </summary>
public string modifyTime { get; set; }
} class Program
{
static void Main(string[] args)
{
System.Reflection.MemberInfo info = typeof(T2Class);
TMessgAttribute[] hobbyAttr = (TMessgAttribute[])Attribute.GetCustomAttributes(info, typeof(TMessgAttribute));//修改1.这里需要取特性数据的集合了
Console.WriteLine("类名:{0}", info.Name);
for (int i = 0; i < hobbyAttr.Count(); i++)//修改2.这里需要循环打印了
{
Console.WriteLine("================================================");
Console.WriteLine("创建人:{0}", hobbyAttr[i].createName);
Console.WriteLine("创建时间:{0}", hobbyAttr[i].createTime);
Console.WriteLine("备注消息:{0}", hobbyAttr[i].mess);
Console.WriteLine("修改时间:{0}", hobbyAttr[i].modifyTime);
} Console.ReadKey();
}
} [TMessg("2015-12-20", "zhaopei", "我只是测试自定义特性,不要报错哦,求求你了。", modifyTime = "2015-12-21")]
[TMessg("2015-12-21", "zhaopei", "我再次测试,还能给我面子显示出来吗?", modifyTime = "2015-12-22")]
public class TClass
{
//................
} public class T2Class : TClass
{
//...........
}
}

自定义特性可以干什么?

上面我们通过反编译,发现自定义特性实际上就是一个对象调用的最前面加了一段实例化的代码。

那么我们可以做的事可多了,除了像上面一样为对象设置“注释”,我们还可以自定义个特性,给某些方法或是某些类做“操作日志记录”,或者给需要在执行某些方法的时候需要权限,我们可以做个权限认证的特性等等。

这里就需要大家自己去扩展了。

[巩固C#] 一、特性是什么东东的更多相关文章

  1. Lambda 表达式 是 个 好东东

    Lambda 表达式 是 个 好东东 首先,通过 Lambda 表达式 + 动态语言特性 dynamic , C# 已经 可以 实现 函数式 编程 了 其次, 利用 Lambda, 可以 实现 AOP ...

  2. 【转】DSP是什么--DSP是神马东东??

    原文:http://www.eepw.com.cn/article/272908.htm 导读:本文主要介绍的是DSP是什么,不懂得童鞋们快随小编一起学习一下DSP到底是个神马东东吧! 本文引用地址: ...

  3. DNS是个什么东东

    工作中经常用到或者听到DNS,什么内网服务器,DNS服务器,啥也别想,你问下自己,你知道什么是DNS吗? 如果你非常清楚,OK,那你肯定是鼠标点错了,赶紧关了这个页面吧,如果你不是很清楚,知道一点,那 ...

  4. json是个啥东东

    xml 不用说 只要是搞web开发的 没听说谁不知道的 一种类似数据传输格式定义的语言 但是他却不是一个真正的轻量级的东西 其他的不说 只要传输一点很少的数据 经过他那左括号右括号 还有什么属性 一包 ...

  5. 长见识了,知道了collected和Graphite 这两个东东

    今天下午的讨论会议中,听到了两个名词collected和Graphite这是神马东东,以前在bingo的时候也没听说过,开完会下去查了下.原来他两是监控系统的啊.以前也从来没做过系统监控方面的项目,这 ...

  6. BPEL是个什么东东

    研究团队有个做智能服务组合的,其中用到叫BPEL的东西,因为全称是Business Process Execution Language,译成中文就是商业执行过程语言,这个东东的是整合SOA的一个执行 ...

  7. SQLSERVER 里经常看到的CACHE STORES是神马东东?

    SQLSERVER 里经常看到的CACHE STORES是神马东东? 当我们在SSMS里执行下面的SQL语句清空SQLSERVER的缓存的时候,我们会在SQL ERRORLOG里看到一些信息 DBCC ...

  8. 【UVA 1151】 Buy or Build (有某些特别的东东的最小生成树)

    [题意] 平面上有n个点(1<=N<=1000),你的任务是让所有n个点连通,为此,你可以新建一些边,费用等于两个端点的欧几里得距离的平方. 另外还有q(0<=q<=8)个套餐 ...

  9. JSNI GWT中的东东

    二.JavaScript Native InterfaceJavaScript本地接口JSNI.1)声明一个本地方法在JSNI中声明一个本地方法时,使用Java的标准native关键字,就像在JNI( ...

随机推荐

  1. metasploit 读书笔记-信息收集

    三、信息收集 被动信息收集 在不接触目标系统时进行的信息收集,包括使用工具Yeti、Whois (1)Whois msf > whois secmaniac.net (2)Netcraft:fi ...

  2. Warning: Static member accessed via instance reference

    Warning: Static member accessed via instance reference Shows references to static methods and fields ...

  3. Python-5-字符串方法

    center >>> "The Middle by Jimmy Eat World".center(39) '     The Middle by Jimmy E ...

  4. C语言中的头文件

    1.头文件#include <> :表示引用标准库头文件,编译器会从系统配置的库环境中去寻找 2.头文件#include "":一般表示用户自己定义使用的头文件,编译器 ...

  5. Margarite and the best present

    Little girl Margarita is a big fan of competitive programming. She especially loves problems about a ...

  6. matplolib实例之 城市气候与海洋的关系研究

  7. day22 正则表达式 re

    1. 正则表达式 正则表达式是对字符串操作的一种逻辑公式. 我们一般使用正则表达式对字符串进行匹配和过滤. 工具: 各大文本编辑器⼀般都有正则匹配功能. 我们也可以去http://tool.china ...

  8. git 合并某个提交commit到指定的分支上

    https://blog.csdn.net/anhenzhufeng/article/details/77962943 git checkout master git cherry-pick 62ec ...

  9. Wireshark抓包数据:理解与分析

    wireshark是一个非常好用的抓包工具,本文根据平时抓包经验,对之前wireshark抓包的一些常见知识点进行了整理. 有不当之处,欢迎指正 1.SYN,FIN会消耗一个序号,单独的ACK不消耗序 ...

  10. sshd_config注释

    [root@H0f ~]# cat /etc/ssh/sshd_config #update by H0f -- # $OpenBSD: sshd_config,v // :: djm Exp $ # ...