一、反射
1、什么是反射
-
了解反射之前,要先了解一下元数据。元数据指保存在程序集中的一些有关程序及其类型的数据,包括类、结构、委托、接口和枚举等)的成员和成员的信息。
-
程序在运行时,可以查看程序集以及其本身的元数据,是反射。
-
通过反射,可以在运行时获取程序或程序集中的所有类型的元数据。
-
反射指程序可以访问、检测和修改它本身状态或行为的一种能力。(来源:菜鸟教程)
-
反射的命名空间是System.Reflection。
具体内容:
.NET 中的反射
2、反射的优缺点
首先在编译中分为动态编译和静态编译,静态编译是在编译中确定类型,绑定对象,而动态编译是在运行中确定类型,绑定对象
反射的优点就是可以动态创建对象、绑定对象,提高了程序的灵活性和扩展性,但反射是一种解释操作,在性能上不如静态编译快
3、 反射(Reflection)的用途
反射(Reflection)有下列用途:
- 它允许在运行时查看特性(attribute)信息。
- 它允许审查集合中的各种类型,以及实例化这些类型。
- 它允许延迟绑定的方法和属性(property)。
- 它允许在运行时创建新类型,然后使用这些类型执行一些任务。
4、Type 类
通过反射类的Type,来获取有关构造函数、方法、字段、属性和事件的信息。
Type 是抽象类。在运行时,CLR 创建从 Type(RuntimeType)派生的类的实例,Type 包含了类型信息。当访问这些实例时,CLR 不会返回派生类的引用而是返回 Type 基类的引用。
如声明了一个 MyClass 类型,不管创建了多少个 MyClass 类型的实例,就只有一个 Type 对象表示它。即每一个类型对应一个 Type 类型的对象。
表-System.Type 类的部分成员
成员 | 成员类型 | 描述 |
---|---|---|
Name | 属性 | 返回类型的名字 |
Namespace | 属性 | 返回包含类型声明的命名空间 |
Assembly | 属性 | 返回声明类型的程序集。如果类型是泛型的,返回定义这个类型的程序集 |
GetFields | 方法 | 返回类型的字段列表 |
GetProperties | 方法 | 返回类型的属性列表 |
GetMethods | 方法 | 返回类型的方法列表 |
1)获取 Type 对象
通过 object 对象 来获取 Type 对象。
Type t = myInstance.GetType():
示例:
class BaseClass
{
public int BaseField = 0;
}
class DerivedClass: BaseClass
{
public int DerivedField = 0;
}
class Program
{
static void Main(string[] args)
{
var bc = new BaseClass();
var dc = new DerivedClass();
BaseClass[] bca = new BaseClass[] { bc, dc };
foreach(var v in bca)
{
Type t = v.GetType();
Console.WriteLine($"Object type :{ t.Name }");
FieldInfo[] fi = t.GetFields();
foreach (var f in fi)
Console.WriteLine($" Field :{ f.Name }");
Console.WriteLine();
}
//Arry 枚举器的Type 类型
int[] arr = { 10, 11, 12, 13 };
var arrEnumerator = arr.GetEnumerator();
Type ArrEnumeratorType = arrEnumerator.GetType();
var arrEnumeratorProperties = ArrEnumeratorType.GetProperties();
Console.WriteLine($"Object type :{ ArrEnumeratorType.Name }");
foreach (var item in arrEnumeratorProperties)
Console.WriteLine($" Properties:{ item.Name }");
Console.ReadKey();
}
}
输出结果:
Object type :BaseClass
Field :BaseField
Object type :DerivedClass
Field :DerivedField
Field :BaseField
Object type :SZArrayEnumerator
Properties:Current
2)使用 typeof 运算符来获取 Type 对象。
class BaseClass
{
public int BaseField = 0;
}
class DerivedClass: BaseClass
{
public int DerivedField = 0;
}
class Program
{
static void Main(string[] args)
{
Type tbc = typeof(DerivedClass);
Console.WriteLine($"Object type :{ tbc.Name}");
FieldInfo[] fi = tbc.GetFields();
foreach (var f in fi)
Console.WriteLine($" Field : { f.Name }");
Console.ReadKey();
}
}
输出结果:
Object type :DerivedClass
Field : DerivedField
Field : BaseField
二、特性
1、什么是特性
特性是一种允许我们向程序的程序集添加元数据的语言结构。它是用于保存程序结构信息的特殊类型的类。
- 将应用特性的程序结构叫作目标。
- 设计用来获取和使用元数据的程序(比如对象浏览器)叫作特性的消费者。
- .NET 预定了很多特性,我们也可以声明自定义特性。
关于图中的特性:
- 在源码中将特性应用于程序结构。
- 编译器获取源代码并且从特性产生元数据,然后把元数据放到程序集中。
- 消费者程序可以获取特性的元数据以及程序中其他组件的元数据,注意,编译器同时产生和消费特性。
2、应用特性
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集。
[Serializable]
public class MyClass
{...}
[MyAttribute("Simple class","Version 3.57")]//带有参数的特性
public class MyOtherClass
{...}
3、预定义的保留特性
1)Obsolete 特性
可以使用 Obsolete 特性将程序结构标注为“过时”,以显示有用的警告消息。
class Program
{
[Obsolete("Use method SuperPrintOut")]//将特性应用到方法
static void PrintOut(string str)
{
Console.WriteLine(str);
}
static void Main(string[] args)
{
PrintOut("Start of Main");//调用 Obsolete 方法
}
}
在 VS 底部栏,错误列表里,显示警告信息:
严重性 代码 说明 项目 文件 行
警告 CS0618 “Program.PrintOut(string)”已过时:“Use method SuperPrintOut”…
如将 Obsolete 特性改为:
[Obsolete("Use method SuperPrintOut",true)]
在 VS 底部栏,错误列表里,显示错误信息:
严重性 代码 说明 项目 文件 行
错误 CS0619 “Program.PrintOut(string)”已过时:“Use method SuperPrintOut”…
2)Conditional 特性
Conditional 特性允许我们包括或排斥特定方法的所有调用。
在方法上使用 Conditional 特性的规则:
- 该方法必须是类型或结构体的方法。
- 该方法必须为 void 类型。
- 该方法不能被声明 overrride,但可以标记为 virtual。
- 该方法不能是接口方法的实现。
#define DoTrace
using System;
//....
class Program
{
[Conditional("DoTrace")]
static void TraceMessage(string str)
{
Console.WriteLine(str);
}
static void Main(string[] args)
{
TraceMessage("Start of Main");
Console.WriteLine("Doing work in Main");
TraceMessage("End of Main");
Console.ReadKey();
}
}
若定义了编译符号#define DoTrace,输出结果:
Start of Main
Doing work in Main
End of Main
若没有定义编译符号#define DoTrace,输出结果:
Doing work in Main
3)调用者信息特性
利用调用者信息特性可以访问文件路径、代码行数、调用成员的名称等源代码信息。
- 这3个特性名称为 CallerFilePath、CallerLineNumber 和 CallerMemberName。
- 这些特性只能用于方法中的可选参数。
class Program
{
public static void MyTrace(string message,
[CallerFilePath] string fileName = "",
[CallerLineNumber] int lineNumber = 0,
[CallerMemberName] string callingMember=""
)
{
Console.WriteLine($"File: { fileName }");
Console.WriteLine($"Line: { lineNumber }");
Console.WriteLine($"Called From: { callingMember }");
Console.WriteLine($"Message: { message }");
}
static void Main(string[] args)
{
MyTrace("Simple message");
Console.ReadKey();
}
}
输出结果:
File: C:\Users\用户名\documents\visual studio 2015\Projects\ConsoleApplication2\ConsoleApplication2\Program.cs
Line: 35
Called From: Main
Message: Simple message
4)DebuggerStepThrough 特性
在单步调试代码时,可以通过 DebuggerStepThrough 特性不让调试器进入某些方法里进行调试,而是执行该方法后,直接继续调试下一行代码。
DebuggerStepThrough 特性:
- 该特性位于 System.Diagnostics 命名空间。
- 该特性可用于类、结构、构造函数、方法或访问器。
class Program
{
int x = 1;
int X
{
get { return x; }
[DebuggerStepThrough] //不进入 set 访问器
set
{
x = x * 2;
x += value;
}
}
public int Y { get; set; }
[DebuggerStepThrough]//不进入这个方法
void IncrementFields()
{
X++;Y++;
}
static void Main(string[] args)
{
Program p = new Program();
p.IncrementFields();
p.X = 5;
Console.WriteLine($"X = { p.X }, Y = { p.Y }");
Console.ReadKey();
}
}
调试过程:
4、其他预定义特性
特性 | 意义 |
---|---|
CLSCompliant | 声明公开暴露的成员应该被编译器检测其是否符合 CLS。兼容的程序集可以被任何兼容 .NET 的语言使用 |
Serializable | 声明结构可以被序列化 |
NonSerialized | 声明结构不能被序列化 |
DLLImport | 声明是非托管代码实现的 |
WebMethod | 声明方法应该被作为 XML Web 服务的一部分暴露 |
AttributeUsage | 声明特性能应用于什么类型的程序结构。将这个特性应用到特性声明上 |
5、多个特性
//多层结构
[Serializable]
[MyAtrribute("Simple class","Version 3.57")]
//逗号分隔
[MyAtrribute("Simple class","Version 3.57"),Serializable]
6、其他类型的目标
目标:字段和属性等程序结构
//字段上的特性
[MyAtrribute("Holds a value","Version 3.2")]
public int MyField;
//方法上的特性
[Obsolete]
[MyAtrribute("Prints out a message.","Version 3.6")]
public void PrintOut()
{
...
}
显示地标注特性:
[method: MyAtrribute("Prints out a message.","Version 3.6")]
[return: MyAtrribute("This value represents...","Version 2.3")]
public long ReturnSetting()
{
...
}
表-特性目标:
其中 type 覆盖了类、结构、委托、枚举和接口; typevar 目标名称为使用泛型的结构指定类型参数。
特性目标 | |
---|---|
event | field |
method | param |
property | return |
type | typevar |
assembly | module |
7、全局特性
通过使用 assembly 和 module 目标名称来使用显式目标说明符把特性设置在程序集或模块级别。
- 程序集级别的特性必须放置在任何命名空间之外,并且通常放置在 AssemblyInfo.cs 文件中。
- AssemblyInfo.cs 文件通常包含有关公司、产品以及版权信息的元数据。
AssemblyInfo.cs 文件中代码段:
[assembly: AssemblyTitle("SuperWidget")]
[assembly: AssemblyDescription("Implements the SuperWidget product.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("McArthur Widgets,Inc.")]
[assembly: AssemblyProduct("Super Widget Deluxe")]
[assembly: AssemblyCopyright("Copyright McArthur Widgets 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]