android 集成同一interface不同泛型_C# 基础知识系列- 10 反射和泛型(二)
0. 前言
这篇文章延续《C# 基础知识系列- 5 反射和泛型》,继续介绍C#在反射所开发的功能和做的努力。上一篇文章大概介绍了一下泛型和反射的一些基本内容,主要是通过获取对象的类型,然后通过这个类型对象操作对象。这一篇介绍一个在反射中很重要的内容:特性,以及上一篇未完成的内容——泛型在反射中的引用。
1. 特性
特性是一种类增强技术,配合解析对应的解析方法可以完成很多类原本没有的功能。特性本质是一种标签,可以标注在类、方法、属性等。它是类本身的一种信息扩展,就像生活中一个人只有一个身份证号,但是可以有多个身份一样,而这些多出来的身份对于类来说就是特性。特性虽然是对类的增强,但不局限于在类上做标记,属性、方法上都可以。
在C#中特性分为三种,位映射特性、自定义特性和伪自定义特性。
位映射特性,举个例子,在C#中一个类会有public、private、abstract(抽象类)、saled(不能继承)等修饰符,而这些修饰符在C#编译的过程中会生成一串二进制码,里面存放就是 是否是public、是否是private 等。这些就是位映射特性的一部分,位映射特性对我们来说是无法进行扩展和修改的,所以就不做更多的介绍。
我们通常说的特性一般指的是自定义特性,这部分特性也是我们能够扩展的,也是我们实际开发中用的特性。
1.1 定义一个特性
如何正确的定义一个特性呢?在C#中,特性也是类的一种。所以声明一个特性,就如同声明一个类一样,不同的是,这个类指定一个根父类是System.Attribute
。所有自定义特性都是这个类的子类或者后代类,无一例外。同时,C#提倡在定义一个特性类的时候,类名应当以Attribute
结尾,在使用的时候可以自动忽略。
示例:
public class DemoAttribute : Attribute { }
以上实例就是定义了一个很普通的特性类,用了也没有任何用的特性。因为特性只是一种标签。这个特性类可以用在任何支持特性的地方,当这个特性标记一个类的时候,目标类的子类也将自动获取这个特性。
以上是一个特性的默认行为,如果我们想要对此做一定限制的话,那么就需要用到特性System.AttributeUsageAttribute
。这个特性类用来控制特性的使用方式。
public bool Inherited { get; set; }// 该特性是否可以被子类继承,默认是 True
public bool AllowMultiple { get; set; }// 一个类是否可以多次使用该特性做标记,默认是 False
public AttributeTargets ValidOn { get; }//获取一组值,这组值标识指示的属性可应用到的程序元素,该参数使用构造方法赋值
我们再来看看AttributeTargets
里有些什么吧。
[System.Flags]
public enum AttributeTargets
{Assembly = 1,// 表示特性是用在 Assembly上的,不常用Module = 2, //特性是用在 Module上的,不常用Class = 4, // 表示特性是用来类上的Struct = 8, //表示用在结构体上Enum = 16, // 0x00000010 表示用在枚举上Constructor = 32, // 0x00000020 构造方法Method = 64, // 0x00000040 普通方法Property = 128, // 0x00000080 属性Field = 256, // 0x00000100 字段Event = 512, // 0x00000200 事件Interface = 1024, // 0x00000400 接口Parameter = 2048, // 0x00000800 方法的参数Delegate = 4096, // 0x00001000 委托ReturnValue = 8192, // 0x00002000 返回值GenericParameter = 16384, // 0x00004000 泛型参数All = GenericParameter | ReturnValue | Delegate | Parameter | Interface | Event | Field | Property | Method | Constructor | Enum | Struct | Class | Module | Assembly, // 0x00007FFF ,所有
}
我们常用的限制是ALL或者类等,限制也可以是多个,写法如下:限制A|限制B|限制C
,表示A、B、C三种限制共存。具体原理是因为 AttributeTargets
是支持位运算的枚举,通过一定的位运算可以在一个值中间存放多个枚举。
说了这么多,我们自己重新写一个特性类吧:
1.限定只能给类使用的特性
[AttributeUsage(AttributeTargets.Class)]
public class DemoAttribute : Attribute { }
2.限定只能给方法使用的特性
[AttributeUsage(AttributeTargets.Method)]
public class DemoAttribute : Attribute
{
}
3.限定不能继承的特性
[AttributeUsage(AttributeTargets.All, Inherited = false)]
public class DemoAttribute : Attribute
{
}
4.限定类和枚举可以使用,但不能继承的 特性
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Enum, Inherited = false)]
public class DemoAttribute : Attribute
{
}
需要注意的一点就是,如果要指定是否可以被继承或者是否允许多次使用 这两个属性则需要先指定特性的作用范围,即限定是类能使用还是所有都可以。
1.2 使用特性
我们自定义了一个特性,就必须使用它才能会有意义,否则它只是一个普通的类。那么我们该如何使用呢?其实在上一节中我们隐晦的介绍了特性的使用方式。就是用中括号包裹起来,给类、属性、方法等标记起来。
首先我们定义一个贼普通的特性:
public class DemoAttribute : Attribute { }//没有任何限制,可以用在任何支持特性的地方
然后使用它:
[Demo]//[DemoAttribute]
public class TestDemo
{
}
如示例所示,在类上面添加[Demo]
标记,表示这个类应用了特性DemoAttribute,也可以使用类名,但是C#会自动忽略类名中结尾的Attribute。当然有的人会把特性写在类或者方法等声明的同一行开头位置,不过我一般会写在不同行,毕竟阅读上简单明了。
我们之前说过,抛开它集成自Attribute类不提,它也是一个类。既然是类,那么就会有属性。那么现在定义一个带属性的特性类:
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Enum, Inherited = false)]
public class DemoAttribute : Attribute
{public string Name { get; set; }
}
该特性声明了一个变量,使用方式如下:
[Demo(Name = "测试")]
public class Student{ }
DemoAttribute是一个只能用在 类、枚举 上的特性,有一个属性是Name。在使用的时候可以用(属性名="属性值")
的方式为属性赋值。
更多的使用方式:
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Enum, Inherited = false)]
public class DemoAttribute : Attribute
{public string Name { get; set; }public int Age { get; set; }public DemoAttribute(int age){Age = age;}
}
使用:
[Demo(10,Name = "测试")]
public Student(){ }
如果特性类声明了构造方法,那么在使用的时候,优先按照构造方法的顺序进行赋值,然后使用属性名=属性值
的方式为其他属性进行赋值。
2. 特性在反射中的应用
在第一节中介绍了如何声明一个特性和使用特性,但是没有反射或者类加载技术,那么特性的作用就并没有想象中的那么大。就像人有多个身份,但是也得有对应的公司或者对应的环境。比如说,王XX有个身份是某XX公司老总,那么XX公司得需要在工商局注册登记,他这个身份才会有效。如果没有登记,那么这个身份也就是个虚名。当特性离开了反射,离开了类加载技术,特性就是摆设。当然这部分只限于自定义特性,因为C#内置的一些特性涉及到另外的技术:动态编译,或者需要编译器的配合。我们自定义的特性显然没有这些特权,所以必须我们手动开发对应的行为和规范。
首先,声明一个类和特性:
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Enum, Inherited = false)]
public class DemoAttribute : Attribute
{public string Name { get; set; }public int Age { get; set; }public DemoAttribute(int age){Age = age;}
}
[Demo(10,Name = "测试")]
public class Student
{
}
2.1 获取类的特性
var stuType = typeof(Student);
上述代码先获取到一个类的类型对象,然后调用:
IEnumerable<CustomAttributeData> attrs = stuType.CustomAttributes;
将获取到这个类上声明的所有的自定义的特性,不过获取到的是一个CustomAttributeData
,这个类封装了一个特性的特征,但是在我们使用起来会很困难,而且我们更多的需要得到特性本身的对象,而不是这种需要我们进一步处理的对象。那么,调用:
IEnumerable<Attribute> data = stuType.GetCustomAttributes(typeof(DemoAttribute));
通过上述方法就可以获取到一组类型是DemoAttribute
的特性对象。
那么回想一下为什么是一组?在AttributeUsageAttribute
有一个AllowMultiple
属性,这个属性就是用来标记这个特性是否可以标注多个,也就是在同一目标上多次使用,如果这个值为True,则在此处将获取不定个,否则最多一个。具体取决于对目标做了多少标记。
获取到特性之后,依据实际需求进行开发。这里就不做过多介绍了,在后续篇幅中会对这部分的使用做更多的介绍。
2.2 获取其他元素的特性
特性不止可以标记在类上,还可以标记在属性、方法上。那么这些元素应该如何获取对应的特性呢?
1. 属性
var stuType = typeof(Student);
var property = stuType.GetProperties()[0];//假设类有一个Property
var attrs = property.GetCustomAttributes(typeof(DemoAttribute));
2. 方法
var stuType = typeof(Student);
var method = stuType.GetMethods()[0];// 假设类有一个方法
var attrs = method.GetCustomAttributes(typeof(DemoAttribute));
需要注意的地方是,
var
关键字;DemoAttribute只是一个代指,不是特指之前声明的DemoAttribute特性类,因为之前声明的特性类没有对属性和方法进行支持,所以在本节中直接使用会编译不通过。
特性就先简单的介绍到这里,特性可以标记给很多目标比如程序集、模块、类等一系列,但实际开发至少是Web开发中,更多的是标记类、方法、属性等。这里只是介绍了特性的声明和使用,但是没有介绍实际开发中特性的使用,这部分有机会在后续篇幅中介绍吧。因为我也用的不是很多。
3. 反射中的泛型
之前在《C# 基础知识系列- 5 泛型和反射》介绍过,C#的泛型不会在编译过程中抹去痕迹,意思就是我们可以通过反射获取到对象的实际泛型类型。那么如何获取呢?
var stuType = typeof(Student);
// 获取类的泛型参数
var genericTypes = stuType.GenericTypeArguments;var method = stuType.GetMethods()[0];
// 获取方法的泛型参数
var types = method.GetGenericArguments();
这个问题,在我写Java代码的时候,困扰了我很久,没有很好的办法。但是在C#中,我可以不用考虑这个问题。
4. 总结
反射在各大编程语言中是一个很重要的特点,泛型、特性在泛型中扮演着很重要的角色。反射在实际开发中扮演着很重要的角色,但是我们在开发中必须慎重考虑反射的使用。
到目前为止,反射介绍告一段落,但这不是结束。因为反射是个可深可浅的内容,目前只是介绍了依稀概念和理论上的一些内容,而更多的则隐藏在实际开发中,这时候就需要结合需求进行设计和代码编写了。
更多内容烦请关注我的博客《高先生小屋》
android 集成同一interface不同泛型_C# 基础知识系列- 10 反射和泛型(二)相关推荐
- c# getresponsestream返回byte[]_C# 基础知识系列-13 常见类库(三)
0. 前言 在<C# 基础知识系列- 13 常见类库(二)>中,我们介绍了一下DateTime和TimeSpan这两个结构体的内容,也就是C#中日期时间的简单操作.本篇将介绍Guid和Nu ...
- c#物联网_C# 基础知识系列- 16 开发工具篇
0. 前言 这是C# 基础知识系列的最后一个内容讲解篇,下一篇是基础知识-实战篇.这一篇主要讲解一下C#程序的结构和主要编程工具. 1. 工具 工欲善其事必先利其器,在实际动手之前我们先来看看想要编写 ...
- c# contains方法_C# 基础知识系列- 3 集合数组
简单的介绍一下集合,通俗来讲就是用来保管多个数据的方案.比如说我们是一个公司的仓库管理,公司有一堆货物需要管理,有同类的,有不同类的,总而言之就是很多.很乱.我们对照集合的概念对仓库进行管理的话,那么 ...
- c# string 占位符_C# 基础知识系列- 9 字符串的更多用法(一)
0. 前言 在前面的文章里简单介绍了一下字符串的相关内容,并没有涉及到更多的相关内容,这一篇将尝试讲解一下在实际开发工作中会遇到的字符串的很多操作. 1. 创建一个字符串 这部分介绍一下如何创建一个字 ...
- c# 找出目录下的所有子目录_C# 基础知识系列- 14 IO篇 文件的操作(2)
前接上一篇内容. 如果是第一次捧场的小伙伴,为了您阅读的连贯性,烦请扫一眼<C# 基础知识系列- 14 IO篇 文件的操作(1)>.本篇是IO之文件操作的第二篇,介绍一下目录和路径的相关类 ...
- 用c#算成绩的总和_C# 基础知识系列- 6 Lambda表达式和Linq简单介绍
前言 C#的lambda和Linq可以说是一大亮点,C#的Lambda无处不在,Linq在数据查询上也有着举足轻重的地位. 那么什么是Linq呢,Linq是 Language Intergrated ...
- 如何获取exception的target异常_C# 基础知识系列- 15 异常处理篇
0. 前言 为什么我们需要异常处理?什么是异常? 在汉语中,异常指非正常的:不同于平常的.翻译到程序中,就是指会导致程序无法按照既定逻辑运行的意外,或者说是错误.可能会有小伙伴好奇了,我们的程序不是正 ...
- 【问链财经-区块链基础知识系列】 第三十二课 从区块链溯源来看农产品链的设计
在区块链的应用场景中,农产品溯源和食品溯源一直是翻来覆去讲的故事,但实际上溯源是一个伪命题,农产品和食品的加密上链实现溯源看起来很丰满,然而现实却很骨干.我们经常忘记了初心:溯源的目的是为了什么? 农 ...
- [C#基础知识系列]专题十七:深入理解动态类型
本专题概要: 动态类型介绍 为什么需要动态类型 动态类型的使用 动态类型背后的故事 动态类型的约束 实现动态行为 总结 引言: 终于迎来了我们C# 4中特性了,C# 4主要有两方面的改善--Com 互 ...
最新文章
- 再见,谷歌!再见,算法!
- 使用network管理DD恢复全库操作步骤
- JZOJ 3.10 1540——岛屿
- Spring Data JDBC通用DAO实现–迄今为止最轻量的ORM
- 这么香的技术还不快点学起来,含BATJM大厂
- discuz admin.php无法登录,忘记管理员密码无法登录Discuz后台管理员的解决方法汇总...
- igs无法分配驱动器映射表_硬盘无法使用,用DiskPart进行分区和格式化,非常简单...
- win7打不开qq文件怎么办
- 抵制羊毛党,图计算“加持”互联网电商风控
- 浅谈APP与H5对比!优势都有哪些呢?
- 数字后端基本概念介绍——Placement Blockage
- python web环境傻瓜搭建_工具赋能Python环境搭建
- php json对象转为字符串,JSON对象转化为字符串(附上代码详细解答)
- 机器码、序列号、认证码、注册码的生成算法(一)
- Activity启动流程(三)Activity Task调度算法复盘分析
- 创建带有实时在线数据备份的指纹考勤系统
- 参考文献标引方式_参考文献的标注方法有哪些呢?
- 中国SaaS驶入「2.0时代」
- Unity鼠标光标使用学习
- 信息管理系统(五)战略规划与发开方法
热门文章
- android 扫描重复文件,Android Gradle在APK META-INF中复制的重复文件
- shell脚本给mysql创建表_shell脚本:实现MySQL创建数据库和删除数据库的脚本
- 山东大学 2020级数据库系统 实验六
- Atmel跑Linux的arm芯片,Linux已被移植到Atmel的ATmega微控制器
- 小程序开发之基础知识(0)
- ip地址自动切换器_网络IP切换器IPProviders下载_IPProviders官方版下载1.1.22
- 42021高考成绩查询北京6一个,北京高考成绩查询时间:6月27日
- TensorFlow2-操作
- node中间件KOA函数
- HttpContext HttpRuntime