反射是指对程序集重的元数据进行检查的过程。当C#被编译成CIL时,它会维持关于代码的大部分元数据(以前代码被编译成机器语言时,会丢弃元数据)。
利用反射可以枚举出程序集中的所有类型,找出满足特定条件的那些。
读取类型的元数据,关键在于获得System.Type的一个实例,它代表目标类型实例。System.Type提供了获取类型信息的所有方法。获得对类型的Type的引用主要通过object.GetType() 或typeof()
//object.GetType():获取一个对象实例
//静态类无法使用,因为静态类无法实例化
DateTime dateTime = new DateTime();
Type type = dateTime.GetType();
foreach (var item in type.GetProperties())
{
Console.WriteLine(item.Name);
}
//Enum.Parse():获取标识了一个枚举的type对象,将一个字符串转换成特定的枚举值
ThreadPriorityLevel level = (ThreadPriorityLevel)Enum.Parse(typeof(ThreadPriorityLevel), "Idle");
demo没怎么看懂,先放着
CLR2.0新增了一些列方法来判断给定类型是否支持泛型参数和泛型实参:
Type type;
type = typeof(Nullable<>);
Console.WriteLine(type.IsGenericType); //指示类型是否为泛型
Console.WriteLine(type.ContainsGenericParameters); //指示类型是否包含泛型实参
type = typeof(Nullable);
Console.WriteLine(type.ContainsGenericParameters);
Console.WriteLine(type.IsGenericType);
从泛型获取泛型实参:
Stack stack = new Stack();
Type type=stack.GetType();
//调用GetGenericArguments()可获得一个由System.Type实例构成的一个数组,实例顺序是它们被声明的顺序
foreach (var item in type.GetGenericArguments())
{
Console.Write(item.Name+" "+ item.FullName);
}
输出:Int32 System.Int32
nameof操作符生成一个常量字符串来包含被指定为实参的任何程序元素的非限定名称。由于这个行动是在编译时发生,所以nameof技术上说不是反射。但它接收的是有关程序集及其结构的数据
使用nameof而不是直接写字符串有两方面好处:
public class Person:INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private string name = "";
public string Name
{
get { return name; }
set
{
if(value!=name)
{
name = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name));
}
}
}
}
特性是将额外数据关联到属性(以及其他构造)的一种方式。
除了修饰属性,特性还可修饰类、接口、结构、枚举、委托、事件、方法、构造方法、字段、参数、返回值、程序集、类型参数和模块。
程序集的特性用于添加有关程序集的额外元数据。
[assembly: AssemblyTitle("Meter")]
[assembly: AssemblyDescription("Copyright © 2016, All Rights Reserved.")]
[assembly: AssemblyConfiguration("")] //为程序集指定生成配置 debug或release
[assembly: AssemblyCompany("Shenzhen iTest Technology Co., LTD.")]
[assembly: AssemblyProduct("Meter")]
[assembly: AssemblyCopyright("Copyright © iTest 2016")] //版权
[assembly: AssemblyTrademark("Meter")] //商标
[assembly: AssemblyCulture("")] //区域性
[assembly: AssemblyVersion("3.0.0.61")]
[assembly: AssemblyFileVersion("3.0.0.61")]
自定义特性需继承Attribute
查找特性需要给定一个PropertyInfo对象(通过反射来获取),调用GetCustomAttributes(),返回一个object数组,该数组可成功转换为Attribute数组。
为构造应用特性时,通常只有常量值和typeof表达式才允许被作为实参。
如特性有必须的属性值,要提供只能取值的属性(赋值设为私有)
System.AttributeUsageAttribute可以限制特性能修饰哪些构造,只要特性被不恰当使用,就会导致编译错误
[AttributeUsage(AttributeTargets.Property|AttributeTargets.Field, AllowMultiple = false)]
internal class SwitchCommandAttribute: Attribute
{
}
AttributeTargets提供了“运行时”允许特性修饰的所有目标的列表
AttributeUsageAttribute中的AllowMultiple 时具名参数。具名参数在特性构造函数调用中设置特定的公共属性和字段——即使构造函数不包括对应参数。
对具名参数的赋值只能放在构造函数的最后一部分进行,任何显示声明的构造函数参数都必须在它之前完成赋值。
有了具名参数后,就可以直接对特性的数据进行赋值,不必为特性属性的每一种组合都提供对应的构造函数。
预定义特性会影响编译器的行为,有时会导致编译器报错。且,预定义特性没有运行时代码,而是由编译器内建了对它的支持
指示编译器应忽略方法调用或属性,除非定义了指定的条件编译符号。可应用与类和无返回值且不包含out参数的方法。
被ConditionalAttribute修饰的构造仍会进行编译并包含在目标程序集中,具体是否调用取决于调用者所在程序集中的预处理器标识符。
ConditionalAttribute修饰自定义特性时,只有在调用程序集中定义了条件字符串,才能通过反射来获取该自定义特性。
#define CONDITION_A
using System;
using System.Diagnostics;
using System.Reflection;
public class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Begin...");
MethodA();
MethodB();
Console.WriteLine("End...");
}
[Conditional("CONDITION_A")]
private static void MethodA()
{
Console.WriteLine("MethodA executing...");
}
[Conditional("CONDITION_B")]
private static void MethodB()
{
Console.WriteLine("MethodB executing...");
}
}
Begin...
MethodA executing...
End...
用于编制代码版本,向调用者指出一个特定的成员或类型已过时。调用被ObsoleteAttribute修饰的成员,会使编译器显示警告(也可选择报错)
ObsoleteAttribute特性还提供另外两个构造函数,一个是ObsoleteAttribute(string? message),能在编译器生成的报告过时的消息上附加额外的信息,譬如告诉用户用什么来替代已过时的代码。另一个是ObsoleteAttribute(string? message, bool error),bool参数指定是否强制将警告视为错误。
(4条消息) 【C#】序列化_阿月浑子2021的博客-CSDN博客
文档这样的对象可能用程序集的一个版本序列化,以后用另一个版本反序列化。但哪怕只是添加一个新字段,对原始文档进行反序列化的时候都会抛出异常,容易造成版本不兼容问题。(上面那个XmlSerializer 序列化的例子我试了一下,增减字段后读原始文件并没有报错)
为避免这个问题,.Net Framework 2.0及其后续版本添加了一个System.Runtime.Serialization.OptionalFieldAttribute。如需向后兼容,就必须使用OptionalFieldAttribute来修饰序列化的字段——包括私有字段。如果以后的某个版本规定该字段是必须而非可选,就不必添加OptionalFieldAttribute修饰。
C#4.0动态对象的初始实现中,提供了一下4种绑定方式:
(以上四句,不明觉历)
反射的关键功能之一就是动态查找和调用特定类型的成员,这要求在执行时识别成员名或其他特征,比如特性。
C#4.0新增的动态对象提供了更简单的方法来通过反射调用成员,该技术的限制在于,编译时要知道成员名和签名(参数个数,以及指定的参数是否和签名类型兼容)
dynamic data = "Hello!";
Console.WriteLine(data);
data=(double)data.Length;
data *= 3.5;
Console.WriteLine(data);
Hello!
21
将data声明为dynamic类型,并直接在它上面调用方法,编译时不会检查指定成员是否可用,甚至不会检查dynamic对象的基础类型是什么。
dynamic类型实际上是一个object,事实上,如果没有任何调用,dynamic类型的声明和object没有区别。但一旦调用它的成员,区别显露出来了。
为调用成员,编译器要声明System.Runtime.CompilerServices.CallSite
另外还会动态定义一个方法,该方法需要传递CallSite site,object dynamcTarget 和string reuslt三个参数。其中,site是调用点本身,dynamcTarget 是要在上面调用方法的object,result是ToString()方法调用的基础类型的返回值。
得到CallSIte实例后调用CallSite
dynamic 的一些特征: