元数据:有关程序及其类型的数据被称为元数据,它们保存在程序的程序集中
反射
反射:反射指程序可以访问 检测和修改它本身状态或行为的一种能力,程序在运行时,可以查看其他程序集或其本身的元数据,一个运行的程序查看本身的元数据或其他程序的元数据的行为叫做反射。可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后可以调用类型的方法或访问其字段和属性。
优点:
(1)反射提高了程序的灵活性和扩展性
(2)降低耦合性,提高自适应能力
(3)它允许程序创建和控制任何类的对象,无需提前硬编码目标类
缺点:
(1)性能问题:使用反射基本上是一种解释操作,用于字段和方法接入时要慢于直接代码,因此反射机制主要对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用
(2)使用反射会模糊内部逻辑,程序员希望在源代码中看到程序的逻辑,反射却绕过了源代码的技术,因而会带来维护的问题,反射代码比相应的直接代码更复杂。
用途:
(1)它允许在运行时查看特性(attribute)信息
(2)它允许审查集合中的各种类型,以及实例化这些类型
(3)它允许延迟绑定的方法和属性
(4)它允许在运行时创建新类型,然后使用这些类型执行一些任务
Type类
BCL声明了一个叫做Type的抽象类,它被设计用来包含类型的特性,使用这个类的对象能让我们获取程序使用的类型的信息,由于Type是抽象类,因此它不能有实例,而是在运行时,CLR创建从Type(RuntimeType)派生来的实例,Type包含了类型信息。当我们要访问这些实例时,CLR不会返回派生类的引用而是Type基类的引用。
(1)对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象
(2)程序中用到的每一个类型都会关联到独立的Type类的对象
(3)不管创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例
(4)使用以下相关成员以及对应类型时需要包含using System.Reflection;命名空间
获取Type对象(GetType、typeof)
GetType:
object类型包含了一个叫做GetType的方法,它返回对实例的Type对象的引用,由于每一个类型最终都是从object继承的,所以我们可以在任何类型对象上使用GetType方法来获取它的Type对象,例如:Type t=myInstance.GetType();
class A
{
public int i;
public float f;
public long l;
public double d;
public void foo() { }
public int fun() { return 10; }
}
class B : A
{
public int b;
}
class Program
{
static void Main(string[] args)
{
A a = new A();
B b = new B();
A[] aa = new A[] { a, b };
foreach(var v in aa)
{
Type t = v.GetType(); //获取类型
Console.WriteLine("Object Type:{0}", t.Name);
FieldInfo[] fi = t.GetFields(); //获取字段信息
foreach (var v2 in fi)
Console.WriteLine(" Field:{0}", v2.Name);
Console.WriteLine();
}
}
}
对应打印出:
Object Type:A
Field:i
Field:f
Field:l
Field:d
Object Type:B
Field:b
Field:i
Field:f
Field:l
Field:d
typeof:
只需要提供类型名作为操作数,typeof运算符就会返回Type对象的引用。
class Program
{
static void Main(string[] args)
{
Type t1 = typeof(A);
Console.WriteLine("Object Type:{0}", t1.Name);
FieldInfo[] fi = t1.GetFields();
foreach (var v in fi)
Console.WriteLine(" Field:{0}", v.Name);
}
}
特性
特性:是一种允许我们向程序的程序集增加元数据的语言结构,它可以在运行时传递各种元素(类、方法、结构、枚举、组件等)的行为信息的声明性标签,它是用于保存程序结构信息的某种特殊类型的类。特性的目的是告诉编译器把程序结构的某组元素嵌入程序集,可以通过使用特性向程序添加生命性信息。
(1)特性片段被[ ]包围,其中是特性名和特性的参数列表
(2)在结构前放置特性片段来应用特性
(3)大多数特性只针对直接跟随在一个或多个特性片段后的结构
(4)应用了特性的结构称为被特性装饰(decorated或adorned,俩者都应用得很普遍)
预定义特性
-----Obsolete(过期的):
一个程序可能在其生命周期中经历多次发布,出于多种原因,会需要编写新的方法来代替老方法,而你希望以后其他程序员也不再使用老方法,这时可以使用Obsolete特性将程序结构标注为过期的,并且在代码编译时显示有用的警告或直接报错。
class A
{
[Obsolete("此函数已经过时了")] //将特性应用到方法
//[Obsolete("此函数已经过时了",true)] //增加第二个参数true,将警告标记为错误
static public void foo() { Console.WriteLine(111); }
}
class Program
{
static void Main(string[] args)
{
A.foo(); //可以正常执行,但在编译过程中会产生CS0618的警告,告诉我们使用过时的函数
}
}
第一种一个参数的形式会在编译中产生一个CS0618的警告,并输出参数填写的信息,第二种重载了接收bool类型的第二个参数,这个参数指定目标是否应该被标记为错误而不仅仅是警告。
-----Conditional(包括或排斥):
Conditional特性允许我们包括或排斥特定方法的所有调用,Conditional特性把编译符(在C#是用#define来定义编译符的)作为参数来使用,如果定义了该编译符号,那么编译器会包含所有调用这个方法的代码,这个普通方法没有什么区别,如果没有定义该编译符号,那么编译器会忽略代码中这个方法的所有调用,定义方法的CIL代码本身总是会包含在程序集中,只是调用代码会插入或忽略。
Conditional条件方法的限制:
(1)条件方法必须是类或结构中声明的方法,如果是在接口中声明的方法指定Conditional属性,将出现编译错误
(2)条件方法必须具有返回类型
(3)不能用override修饰符标记条件方法,但是可以用virtual修饰符标记条件方法
(4)不能为用于委托创建表达式中的方法添加Conditional属性,否则会编译时出错
#define aa //定义名为aa的编译符 #define必须在文件的最开始位置定义
using System.Diagnostics;
using System;
namespace lianxi
{
class A
{
[Conditional("aa")]
static public void foo()
{
Console.WriteLine(111);
}
}
class Program
{
static void Main(string[] args)
{
A.foo();
}
}
}
如代码所示,#define定义编译符只能在文件的最开始位置定义,如果定义了aa这个编译符则调用foo时会正常打印出111,如果没有定义aa,则编译器会忽略对foo的调用代码,就不会打印出任何东西。
-----CallerFilePath、CallerLineNumber、CallerMemberName调用者信息特性:
以上的调用者信息特性分别是可以访问文件路径、代码行数、调用成员的名称等源代码信息,这些特性只能用于方法的可选参数,使用这些特性时需要添加头文件using System.Runtime.CompilerServices; 且这些特性只能应用于具有默认值的参数。
using System;
using System.Runtime.CompilerServices; //使用调用者信息特性应该增加的头文件
namespace lianxi
{
class A
{
static public void foo([CallerFilePath] string fileName = "", [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string callingMember="")
{
Console.WriteLine("File: {0}",fileName);
Console.WriteLine("Line: {0}", lineNumber);
Console.WriteLine("Called From: {0}", callingMember);
}
}
class Program
{
static void Main(string[] args)
{
A.foo();
}
}
}
输出为:
File: C:\Users\wenmai\Desktop\practice\lianxi\lianxi\Program.cs
Line: 20
Called From: Main
-----DebuggerStepThrough(禁止调试时进入):
我们在单步调试代码时,常常希望调试器不要进入某些方法,我们只想执行该方法,然后继续调试下一行,DebuggerStepThrough特性告诉编译器在执行目标代码时不要进入该方法调试,使用该特性需要添加头文件using System.Diagnostics; 该特性可用于类、结构、构造函数、方法或访问器
其他预定义特性
多个特性的书写
(1)独立的特性片段相互叠在一起
(2)单个特性片段,特性之间使用逗号分隔
自定义特性
特性只是某个特殊类型的类,用户自定义的特性类叫自定义特性,声明一个特性类和声明其他类一样,但是它有一些注意事项:
(1)该类需派生自System.Attribute类
(2)该特性以后缀Attribute结尾
(3)为了安全起见,通常建议声明一个sealed的特性类
特性和其他类一样,都有构造函数,每个特性至少必须有一个公共构造函数,如果没有声明构造函数,则编译器会自动产生一个隐式、公共、无参的构造函数,构造函数可以被重载,声明构造函数时必须使用类全名,包括后缀。我们只可以在应用特性时使用短名称。
特性类的构造函数
当我们为目标应用特性时,其实是在指定应该使用哪个构造函数来创建特性的实例,列在特性应用中的参数其实就是构造函数的参数,在应用特性时,构造函数的实参必须是在编译期能确定值的常量表达式,如果应用的特性构造函数没有参数,可以省略( )
和其他类一样,我们不能显式调用构造函数,特性的实例创建后,只有特性的消费者访问特性时才能调用构造函数,这一点与其他类的实例很不相同,这些实例都创建在使用对象创建表达式的位置,应用一个特性是一条声明语句,它不会决定什么时候构造特性类的对象。
限制特性(AttributeUsage)的使用
我们可以为类应用特性,而特性本身就是类,有一个很重要的预定义特性可以用来应用到自定义特性上,那就是AttributeUsage特性,我们可以使用它来限制特性使用在某个目标类型上,例如我们希望自定义特性MyAttribute只能应用到方法上,那么可以以如下形式使用AttributeUsage。
特性的访问(IsDefined、GetCustomAttributes)
IsDefined:
我们可以使用Type对象的IsDefined方法来检测某个特性是否应用到了某个类上。
参数1:接收需要检查的特性的Type对象
参数2:bool类型,它指示是否搜索MyClass的继承树来查找这个特性
//特性类
[AttributeUsage(AttributeTargets.Class)]
public sealed class MyAttributeAttribute : System.Attribute
{
}
[MyAttribute]
public class Program
{
static void Main(string[] args)
{
Program p = new Program();
Type t = p.GetType();
bool isDefined = t.IsDefined(typeof(MyAttributeAttribute), false);
if (isDefined)
Console.WriteLine("{0}含有MyAttributeAttribute特性", t.Name);
}
}
GetCutomAttributes:
此方法返回应用到结构的特性数组,实际返回的对象是object数组,因此我们必须将它强制转换为相应的特性类型,布尔值参数指定是否搜索继承树来查找特性,调用此方法后,每一个与目标相关联的特性的实例就会被创建。例如调试下面的代码在此方法调用后会进入MyAttributeAttribute的构造函数中去。
//特性类
[AttributeUsage(AttributeTargets.Class)]
public sealed class MyAttributeAttribute : System.Attribute
{
public string ss1;
public string ss2;
public MyAttributeAttribute(string s1,string s2)
{
ss1 = s1;
ss2 = s2;
}
}
[MyAttribute("aaa","bbb")]
public class Program
{
static void Main(string[] args)
{
Type t = typeof(Program);
object[] AttArr = t.GetCustomAttributes(false);
foreach(Attribute a in AttArr)
{
MyAttributeAttribute attr = a as MyAttributeAttribute;
if(attr!=null)
{
Console.WriteLine("ss1:{0}", attr.ss1); //ss1:aaa
Console.WriteLine("ss2:{0}", attr.ss2); //ss2:bbb
}
}
}
}