⼀、特性是什么
阅读⽬录
前⾔
我们初学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}", ateTime);
Console.WriteLine("创建⼈:{0}", ateName);
Console.WriteLine("备注消息:{0}", ss);
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中设置的特性元数据。
View Code
反之,我们设置 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(TMessg
Attribute));//修改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);
}
writeline方法属于类Console.ReadKey();
全部代码:
View Code
⾃定义特性可以⼲什么?
上⾯我们通过反编译,发现⾃定义特性实际上就是⼀个对象调⽤的最前⾯加了⼀段实例化的代码。
那么我们可以做的事可多了,除了像上⾯⼀样为对象设置“注释”,我们还可以⾃定义个特性,给某些⽅法或是某些类做“操作⽇志记录”,或者给需要在执⾏某些⽅法的时候需要权限,我们可以做个权限认证的特性等等。
这⾥就需要⼤家⾃⼰去扩展了。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论