反射的介绍与使用 (继续完善中。。。。)
可以看下别人写的相关:
1. 概述反射
- 通过反射可以提供类型信息,从而使得我们开发人员在运行时能够利用这些信息构造和使用对象。
- 反射机制允许程序在执行过程中动态地添加各种功能。
- 反射在项目中会用到的地方, 一般不是因为要实现业务逻辑, 更多的是因为要更好的代码管理. 这个动机很重要.
2. Type类的介绍
- 是BCL(基底类别库)声明的一个抽象类,所有它不能被实例化
- 对于程序中用到的每一个类型,CLR(公共语言运行时)都会创建一个包含这个类型信息的Type类型的对象
- 程序中用到的每一个类型都会关联到独立的Type类型的对象
- 不管创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例
2.1 Type类的部分常见成员
成员 成员类型 描述 Name 属性 返回类型的名字 FullName 属性 返回数据类型的完全限定名(包括命名空间名) NameSpace 属性 返回包含数据类型声明的命名空间 Assembly 属性 返回声明类型的程序集。如果类型是泛型的,返回定义这个类型的程序集 GetConstructor(), GetConstructors() 方法 返回ConstructorInfo类型,用于取得该类的构造函数的信息 GetEvent(), GetEvents() 方法 返回EventInfo类型,用于取得该类的事件的信息 GetField(), GetFields() 方法 返回FieldInfo类型,用于取得该类的字段(成员变量)的信息 GetInterface(), GetInterfaces() 方法 返回InterfaceInfo类型,用于取得该类实现的接口的信息 GetMember(), GetMembers() 方法 返回MemberInfo类型,用于取得该类的所有成员的信息 GetMethod(), GetMethods() 方法 返回MethodInfo类型,用于取得该类的方法的信息 GetProperty(), GetProperties() 方法 返回PropertyInfo类型,用于取得该类的属性的信息
3. 如何获取Type类型
3.1GetType()和typeof() 方法 两者都是返回Syetem.Type的引用。(private和protected修饰的成员也可以访问到)
3.1.1 GetType()
1.GetType()是从Syetem.object中基础的方法。
2.GetType()必须要通过类型的实例点出这个方法。
3.1.2 typeof()
1.typeof(xx)是公开的运算符。
2.typeof(xx)中xx只能是int,string 等类型及自定义类型,不能是实例。
3.2 不同点
1.GetType()返回的是Type(类型)
2.typeof(xx) 返回的是xx Class(类)的类型
//实例一个用户类 User user = new User(); //GetType()方法 Type getType = user.GetType(); //typeof(xx) 方法 Type typeOf = typeof(User); //判断是否相等 if (getType == typeOf) { //这里相等 Console.WriteLine("这里相等"); }
4.Type类方法
1: 一旦有了Type对象就可以使用GetMethodInfo()方法获取此类型支持的方法列表。该方法返回一个MethodInfo 对象数组,MethodInfo对象描述了主调类型所支持的方法,他位于System.Reflection命名空间中
2: MethodInfo类派生于MethodBase抽象类,而MethodBase类继承了MemberInfo类。因此我们能够使用这三个类定义的属性和方法。例如,使用Name属性得到方法名称。这里有两个重要的成员:
3: ReturnType属性 :为Type类型的对象,能够提供方法的返回类型信息 GetParameters()方法 :返回参数列表,参数信息以数组形式保存在PatameterInfo对象中。
PatameterInfo类定义了大量描述参数信息的属性和方法。这里也列出两个常用的属性 :Name(包含参数名称信息的字符串),ParameterType(参数类型的信息)。
//创建实例 Sublevel sublevel = new Sublevel(); //获取类型 Type sublevelType = sublevel.GetType(); //获取类型的方法列表 //BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public 这个有一个注意点 //实际上至少要有Instance(或Static)与Public(或NonPublic)标记。否则将不会获取任何方法。 MethodInfo[] obj = sublevelType.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); //遍历所有的方法 foreach (MethodInfo item in obj) { //返回方法的返回类型 Console.Write(item.ReturnType.Name); //返回方法的名称 Console.Write(" "+item.Name+"("); //获取方法的返回参数列表 ParameterInfo[] parameterss = item.GetParameters(); foreach (var parameters in parameterss) { //参数类型名称 Console.Write(parameters.ParameterType.Name); //参数名称 Console.Write(" "+parameters.Name+","); } Console.WriteLine(")"); }

执行的结果,获取了所有的方法,包括了方法的方法名称,方法的返回类型,方法参数列表。
5.方法的使用
前面我们讲了放的显示,但是只能看到不能用就不好了呀!!!
5.1:GetMethods()方法的另一种形式
这种形式可以制定各种标记,已筛选想要获取的方法。他的通用形式为:MethodInfo[] GetMethods(BindingFlags bindingAttr)BindingFlags是一个枚举,枚举值有(很多只列出4个吧)
- Instance:获取实例方法
- NonPublic: 获取非公有方法
- Public: 获取共有方法
- Static:获取静态方法
GetMethods(BindingFlags bindingAttr)这个方法,参数可以使用or把两个或更多标记连接在一起,
实际上至少要有Instance(或Static)与Public(或NonPublic)标记。否则将不会获取任何方法。del.GetType();
public static void Method<T>(T model) { //获取泛性的Type类型 Type objType = model.GetType(); //获取泛性的方法列表 MethodInfo[] mthodInfos = objType.GetMethods(); //循环方法 foreach (var item in mthodInfos) { //获取方法的所有参数列表 var parameters = item.GetParameters(); //过滤没用方法 //1:查看是不是有参数的方法 //2:查看这个方法的返回类型是不是我们想要的 //3:查看这个方法的返回类型是不是我们想要的 if (parameters.Any() && parameters[0].ParameterType == typeof(int) && item.ReturnType != typeof(void)) { //调用方法 object[] parametersObj = new object[] { 5 }; //调用实例方法 //第一个参数是我们的实体,后面是我们的参数(参数是一个数组,多个参数按照顺序来传递,没有参数可以为null) //如果我们的方法是一个静态方法 ,这个参数可以为null (不是静态的就会报错) Console.WriteLine(item.Invoke(model, parametersObj)); } } }

6.DataTable转Model(List)
在刚刚学.net 的时候,我们从数据库查询出一个DataTable的时候想要转成Model或者LIst的时候我们需要手动的写遍历,超级麻烦(在没有接触MVC的时候我就是)
/// <summary> /// DataTable转换 /// </summary> public class TransitionDataTable { /// <summary> /// DataTable转换模型 /// </summary> /// <typeparam name="T">模型类型</typeparam> /// <param name="obj">模型</param> /// <param name="data">数据行</param> /// <returns></returns> public T DataSetBindModel<T>(T obj, DataTable data) where T : class, new() { T result = new T(); foreach (DataRow item in data.Rows) { result = assignmentClass(obj, item); } return result; } /// <summary> /// DataTable转换List /// </summary> /// <typeparam name="T">模型类型</typeparam> /// <param name="obj">模型</param> /// <param name="data">数据行</param> /// <returns></returns> public List<T> DataSetBindList<T>(T obj, DataTable data) where T : class, new() { List<T> result = new List<T>(); foreach (DataRow item in data.Rows) { result.Add(assignmentClass(obj, item)); } return result; } /// <summary> /// DataRow 转换成模型 /// </summary> /// <typeparam name="T">模型类型</typeparam> /// <param name="obj">模型</param> /// <param name="row">数据行</param> /// <returns></returns> private T assignmentClass<T>(T obj, DataRow row) where T : class, new() { if (obj == null) { obj = new T(); } Type type = obj.GetType(); //得到类型的所有属性,也就是表对应的实体模型的所有属性 //嗮选一下只要公开的 PropertyInfo[] properts = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); if (properts.Any()) { foreach (PropertyInfo item in properts) { if (row.Table.Columns.IndexOf(item.Name) != -1) { if (row[item.Name] != null && row[item.Name] != DBNull.Value) { item.SetValue(obj, row[item.Name]); } } } } return obj; } }
调用
static void Show() { DataTable data = new BDHelper().GetData("select * from Jack_News_TNews"); News news = new News(); var list = new TransitionDataTable().DataSetBindList(news, data); }
7. 反射的操作方式
7.1Get/Set Property Value(Get/Set 属性值设置)
Typescript 可以很直接的动态读写属性值
class Person { name: string = 'default name'; } const person = new Person(); const propertyName = 'name'; console.log('default name', person[propertyName]); // 动态获取属性值 person[propertyName] = 'new name'; // 动态写入属性值 console.log('new name', person[propertyName]);
但是在 C# 是写不到这种语法的, 不包括 dynamic ExpandoObject
C# 要做到上面这种动态读写就需要反射
public class Person { public string Name { get; set; } = "Default Name"; } public class Program { public static void Main() { var person = new Person(); var type = person.GetType(); // 1. 从 object 获取到 Type var property = type.GetProperty("Name")!; // 2. 通过 Type + propertyName (string) 获取到 PropertyInfo var name = (string)property.GetValue(person, null)!; // step 3. 通过 PropertyInfo 的读方法, 获取对象属性值 Console.WriteLine(name); property.SetValue(person, "New Name"); // 3. 通过 PropertyInfo 的写方法, 写入对象属性值 Console.WriteLine(person.Name); } }
流程大概就是:
1. 先反射出对象类型
2. 再反射出属性操作器 (依据 string)
3. 通过操作器, 把 object + object 类型属性操作器 + 值, 搞在一起, 最后就修改掉了对象属性值.
如果只看这一个需求的话, 需要这么多步骤才能做到这点事情, 确实很烦. 但是如果站在一个类型语言要让它"动态"的话, 它的这个步骤就可以做到非常多事情了. 所以我们接着往下.
7.2 Invoke Method (方法调用)
调用方法和读写属性值差不多.
public class Person { public string Name { get; set; } = "Default Name"; public void ChangeName(string newName) { Name = newName; } } public class Program { public static void Main() { var person = new Person(); var type = person.GetType(); var method = type.GetMethod("ChangeName")!; // 从对象类型中, 通过方法名字(string), 获取到方法执行器 method.Invoke(person, new object[] { "new name" }); // 对象 + 方法执行器 + 参数 = 执行了方法 Console.WriteLine(person.Name); } }
找出方法执行器, 然后调用就可以了.
7.3 Optional Parameter (可选参数)
如果遇到 Optional Parameter 需要做一点点需改
public void ChangeName(string newName = "optional new name") { Name = newName; }
method.Invoke(person, new object[] { Type.Missing }); // 使用 Type.Missing 来表示没有传参数
注: Type.Missing 是一定要放的哦, 不放会直接报错的.
7.4 Return Value(返回值)
public string GetName() { return Name; }
var name = (string)method.Invoke(person, Array.Empty<string>())!;
关键就是要强转, 不然返回都是 object 类型.
7.5 Async Task (异步方法)
public async Task DoSomethingAsync() { await Task.Delay(2000); }
await (Task)method.Invoke(person, Array.Empty<string>())!;
关键依然是强转就可以了.
7.6 Async Task<string> (返回类型 异步方法)
public async Task<string> GetNameAsync() { await Task.Delay(2000); return Name; }
var name = await (Task<string>)method.Invoke(person, Array.Empty<string>())!;
也是强转就可以了.
如果不清楚返回的类型, 那么可以用 dynamic (而不是 Task<object> 哦)
注:dynamic (动态类型)
var name = await (dynamic)method.Invoke(person, Array.Empty<string>())!; if (name is string) { Console.WriteLine("name is string"); }
7.7 Anonymous method / Delegate (匿名 方法/委托)
匿名函数, 通常出现在一个方法作用域内.
public static async Task Main() { var @delegate = GetNameAsync; // 注意哦: GetNameAsync 一定要用一个 var 装起来才会变成 Delegate, 如果你直接 GetNameAsync.Method 是不行的 var returnValue = await (Task<string>)@delegate.Method.Invoke(@delegate.Target, new object[] { "value" })!; Console.WriteLine(returnValue); async Task<string> GetNameAsync(string value) { await Task.Delay(2000); return "test"; } }
关键就是把它变成 delegate 然后 delegate.Method 和 delegate.Target 来调用这个方法.
要记得哦, 委托方法和对象是无关的, 所以 Invoke 的时候传入的是 Target 而不是 Object.

比如在类里面有一个委托, 它内部是调用不到 this 的哦.
我们可以通过 get property value 获取到这个委托, 然后通过 delegate.Method 来调用, 它的 target 依然是 delegate.Target 而不是 object.
7.8 Extensions Method (扩展方法)
扩展方法调用起来感觉好像是对象中的方法, 但其实并不是, 它是某个类的静态方法来的, 所以想要获取这个方法的时候一定要找对它的类型. 不可以用 object.GetType()
public class Person { public string Name { get; set; } = "default name"; } public static class PersonExtensions { public static string ReturnName(this Person person, string otherParameter) { return person.Name; } } public class Program { public static async Task Main() { var person = new Person(); // var returnName = person.ReturnName("otherParameter"); // 普通调用会以为 ReturnName under person var type = typeof(PersonExtensions); // 不可以通过 person.GetType() 哦, 因为方法并不在 Person class 内, 而是在 PersonExtensions class 内. var returnNameMethod = type.GetMethod("ReturnName")!; var name = returnNameMethod.Invoke(null, new object[] { person, "otherParameter" }); // 注意: 第一个参数是 null, 因为它是静态方法, 反而是把 object 用作第一个 parameter 传进去. Console.WriteLine(name); } }
7.9 Assembly & Class (程序集 和 类)
面讲了基本 get/set 和方法调用. 现在来看看 Assembly 和 Class 实例化的部分.
如果想通过一个 ClassName string, 来动态创建实例.
Get Assembly Type
通过 class 获取 Assembly, 比较常用在获取 library
typeof(Program).Assembly;
获取当前运行着的 Assembly
Assembly.GetExecutingAssembly();
Create Instance (创建实例)
有了 Assembly 之后就可以用 string 找到 Class 了
var personType = typeof(Program).Assembly.GetType("Reflection.Person")!; // 一定要是 namespace + class name 哦 (也叫 class full name) var instance = (Person)Activator.CreateInstance(personType)!; // 通过 Activator.CreateInstance 来实例化对象, 然后又是强转 Console.WriteLine(instance.Name);
Constructor & Overload (构造函数 和 重载)
即使有重载构造函数也不怕, 它挺聪明的
public class Person { public Person() { } public Person(int age) { } public Person(string name) { } public string Name { get; set; } = "default name"; }
传入参数, 它自己会配对成功哦.
var instance = (Person)Activator.CreateInstance(personType, new object[] { 1 })!;
Constructor + Dependency Injection (构造函数+依赖注入)
安装
dotnet add package Microsoft.Extensions.DependencyInjection
Program.cs
public class SomeService { public string GetValue() => "new value"; } public class Person { public Person (SomeService someService, int age) // 注入 service { Name = someService.GetValue(); } public string Name { get; set; } = "default name"; } public class Program { public static void Main() { var services = new ServiceCollection(); services.AddSingleton<SomeService, SomeService>(); var serviceProvider = services.BuildServiceProvider(); var personType = typeof(Program).Assembly.GetType("Reflection.Person")!; var instance = (Person)ActivatorUtilities.CreateInstance(serviceProvider, personType, new object[] { 5 }); // 通过 ActivatorUtilities 可以传入 serviceProvider Console.WriteLine(instance.Name); } }
做法和之前差不多, 只是换了 ActivatorUtilities.CreateInstance, 然后把 ServiceProvider 传进去就可以了, 虽然用了依赖注入, 但是依然可以传 parameters 哦.
7.10 Generic & Nullable (泛型 和 空)
导入 Generic Type
public class Person1<T> {} public class Person2<T,U> { } var personType1 = typeof(Person1<>).MakeGenericType(typeof(int)); var personType2 = typeof(Person2<,>).MakeGenericType(new Type[] { typeof(int), typeof(string) });
关键就是 Person1<> 里面空的, Person2<,> 里面有一个逗号, 然后后面就是通过 MakeGenericType 去填充 generic type.
导出 Generic Type
var personType2 = typeof(Person2<,>).MakeGenericType(new Type[] { typeof(int), typeof(string) }); Type[] genericTypes = personType2.GetGenericArguments(); Console.WriteLine(string.Join(',', genericTypes.Select(t => t.Name))); // Int32,String
通过 GetGenericArguments 就可以获取所有的 generic types 了.
导出 Nullable
var type = typeof(int?); var same = Nullable.GetUnderlyingType(type) == typeof(int);
7.11 Attribute
C# attribute 和 Typescript 的 decorator 有点像, 但是它更偏向 metadata. 所以也只有反射能获取到它的值了.
public class SomethingAttribute : Attribute { public string Value { get; set; } = ""; } public class Person { [Something(Value = "default value")] public string Name { get; set; } = ""; } public class Program { public static void Main() { var personType = typeof(Person); var nameProperty = personType.GetProperty("Name")!; var something = (SomethingAttribute)nameProperty.GetCustomAttribute(typeof(SomethingAttribute))!; // var something = nameProperty.GetCustomAttribute<SomethingAttribute>()!; // 有时候需求没有太过动态的话, 可以用泛型. Console.WriteLine(something.Value); } }
8 获取反射的类型
8.1 Type Information (类型信息)
在找类型的时候, 除了依据简单的 string 以外, 还会用到很多类型属性来做判断.
比如它是不是 value type (值类型), 它是不是 Enum (枚举), 它是不是 interface (接口), 它是不是继承了某一个类型.
常用到的识别
// is inherit other class var yes1 = typeof(Dog).IsAssignableTo(typeof(Animal)); // true var yes2 = typeof(Animal).IsAssignableFrom(typeof(Dog)); // true var yes3 = typeof(int).IsValueType; // true var yes4 = typeof(string).IsValueType; // false var yse5 = typeof(Enum1).IsEnum; // true var yes6 = typeof(Interface1).IsInterface; // true // have generic ? var yes7 = typeof(HaveGeneric<>).IsGenericType; // true
Is nullable string? (是否空字符串?)
从 .NET 6 开始, 反射可以检查出是不是 nullable string 和 reference 了.
How to use .NET reflection to check for nullable reference type
var type = typeof(Person); var context = new NullabilityInfoContext(); var nameProperty = type.GetProperty("Name")!; var nameInfo = context.Create(nameProperty); var isNullable = nameInfo.ReadState == NullabilityState.Nullable; // true
BindingFlags (绑定标志)
BindingFlags 能在获取 properties, methods, fields, etc. 的时候, 指定 private, public, static, etc. 的范围.
比较常用到的
// 表示查找的时候,需要忽略大小写。 IgnoreCase // 仅查找此特定类型中声明的成员,而不会包括这个类继承得到的成员。 DeclaredOnly // 仅查找类型中的实例成员。 Instance // 仅查找类型中的静态成员。 Static // 仅查找类型中的公共成员。 Public // 仅查找类型中的非公共成员(internal protected private) NonPublic // 会查找此特定类型继承树上得到的静态成员。但仅继承公共(public)静态成员和受保护(protected)静态成员;不包含私有静态成员,也不包含嵌套类型。 FlattenHierarchy
来源: 详解 .NET 反射中的 BindingFlags 以及常用的 BindingFlags 使用方式
常用的方式
var constructors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); var properties = type.GetProperties(); var methods = type.GetMethods(); // 不包括 constructor var fields = type.GetFields(); var members = type.GetMembers(); // 全部东西
默认就是 Public, Instance, Static
Property Info (属性信息)
一些常用到的 property info 判断
public class Person { public string Name1 { get; private set; } = ""; // setter is private public string Name2 { private get; set; } = ""; // getter is private public string Name3 { get; } = ""; // get only public string Name4 { get; init; } = ""; // setter is init // public string SetOnly { set; } = ""; // 报错, 不可以只有 set // private string Name3 { public get; set; } // 报错, 前面 private 后面 public 是不行的 }
1. BindingFlags.Public, 只要前面是 public 就获取得到, setter 是 private 无所谓.
2.CanWrite (是否可以写入)
typeof(Person).GetProperty("Name1")!.CanWrite
除非完全没有 set, 要不然 CanWrite 就是 true, init 依然是 true.
3. GetSetMethod 可以用来判断是不是 private, 参考: How to check if property setter is public
var t1 = typeof(Person).GetProperty("Name1")!.GetSetMethod(); // null var t2 = typeof(Person).GetProperty("Name2")!.GetSetMethod(); // ok var t3 = typeof(Person).GetProperty("Name3")!.GetSetMethod(); // null var t4 = typeof(Person).GetProperty("Name4")!.GetSetMethod(); // ok var person = Activator.CreateInstance(typeof(Person)); typeof(Person).GetProperty("Name1")!.SetValue(person, "dada"); // ok typeof(Person).GetProperty("Name2")!.SetValue(person, "dada"); // ok typeof(Person).GetProperty("Name3")!.SetValue(person, "dada"); // error, can't set typeof(Person).GetProperty("Name4")!.SetValue(person, "dada"); // ok
8.2 Generic(泛型) 高级
var type = typeof(Animal<,>); var yes = type.IsGenericType; // 虽然还没有填充内容, 它依旧是一个 GenericType var yes1 = type.ContainsGenericParameters; // 表示它还没有填充内容 (虽然字面意思好像是反过来 /.\, 但总之 true 就表示还没有 MakeGenericType 就对了...) type.MakeGenericType(new Type[] { typeof(string), typeof(int) }); var yes2 = type.ContainsGenericParameters; // 依然是 true, 因为填充不是 void method var newType = type.MakeGenericType(new Type[] { typeof(string), typeof(int) }); var no = newType.ContainsGenericParameters; // false, 这就表示已经有填充内容了. var yes3 = type == typeof(Animal<,>); // yes var yes4 = newType == typeof(Animal<string,int>); // yes var yes5 = newType.GetGenericTypeDefinition() == typeof(Animal<,>); // yes 通过 GetGenericTypeDefinition 可以返回还没填充的类型
1. MakeGenericType 不是 void method 哦
2. ContainsGenericParameters = true 表示这个 type 还没有被填充 MakeGenericType, false 表示已经有内容了.

3. IsGenericType 不管有没有内容, 只要是 Generic 就是 true
4. GetGenericTypeDefinition 返回还没有填充的 Generic Type
8.3 Class has implement some interface? (类是否实现了一些接口?)
要判断一个 class 是否有 implement 某个 interface, 最好的方式就是拿这个 class type 的所有 interfaces 出来对比
/// <summary> /// 检查类是否实现接口 /// </summary> /// <param name="classType"></param> /// <param name="interfaceType">如果 interface 是空泛型 [,] 那么 class 也将使用空泛型进行比较,如果不是,则 == 比较</param> /// <returns></returns> public static bool HasImplementInterface(Type classType, Type interfaceType) { var requireIsGenericType = interfaceType.IsGenericType; var requireIsEmptyGeneric = interfaceType.ContainsGenericParameters; // is empty <,> ? return classType.GetInterfaces().Where(classInterface => { if (requireIsGenericType && requireIsEmptyGeneric) { if (!classInterface.IsGenericType) return false; return classInterface.GetGenericTypeDefinition() == interfaceType; } else { return classInterface == interfaceType; } }).Any(); }
注: 这里是判断 Type, 如果是实例, 直接 object is Interface 就可以了, 不过那个就不是反射了.
8.4 Get Right Overload Method (获取正确的重载方法)
当方法有重载的时候, 就不可以仅仅通过方法名来获取到正确的方法.
需要判断它的 Generic, 还有它的 Parameters 类型
public static MethodInfo GetMethod(Type classType, string methodName, List<Type> genericTypes = null!, List<Type> parameterTypes = null!) { genericTypes ??= new(); parameterTypes ??= new(); return classType.GetMethods() .Where( m => m.Name == methodName && m.GetGenericArguments().Length == genericTypes.Count && m.GetParameters().Length == parameterTypes.Count ) .Select(m => genericTypes.Count > 0 ? m.MakeGenericMethod(genericTypes.ToArray()) : m) .Single(m => m.GetParameters().Select(p => p.ParameterType).SequenceEqual(parameterTypes)); }
首先是 name, generic count, parameter count 要对
然后把 generic type 填充进去 method type, 然后才可以对比 parameters.
因为 generic 可能就会用到 parameter 上, 如果没有填充好的话, 对比 parameter type 就会错了.
8.5 Get Right Overload Constructor ( 获取正确的重载构造函数)
public static ConstructorInfo GetConstructor(Type classType, List<Type> paramTypes = null!) { paramTypes ??= new(); return classType.GetConstructors() .Where(m => m.GetParameters().Length == paramTypes.Count) .Single(m => m.GetParameters().Select(p => p.ParameterType).SequenceEqual(paramTypes)); }
这个就比较简单, 因为 Constructor 不会有 Generic(泛型), 有 Generic 的是它的 Class
9 反射息息相关的表达式树
首先搞清楚 Delegate, Action, Func, Anonymous Method, Lambda, Expression tree
看大神的文章: C#中的Lambda表达式和表达式树
简单说, Delegate 委托是上古年代的东西,
后来有了泛型, 就演化成 Action 和 Func
然后开始玩匿名函数就有了 Lambda 表达式
然后又出来了一个表达式树.
Lambda 其实就是 Delegate, Action, Func 一样的东西.
而表达式树, 则是一些方法调用的表示手法.
表达式树由很多表达式组成, 经过 compile 可以变成 Lambda, 然后拿去执行.
也可以 parse 这个表达式树, 翻译成其它的东西.
比如 EF Core,
var person = await DbContext.People.Where(p => p.Name == "Derrick").ToListAsync();
上面这一句它可以翻译成 SQL
SELECT * FROM People WHERE Name = 'Derrick';
9.1 Dynamic Expression How It Work (动态表达如何工作)
模拟一下动态创建 Expression for call EF Core's SingleAsync 方法.
public class Person { public string Name { get; set; } = ""; } public class Program { public static void Main() { var people = new List<Person> { new Person { Name = "n1" }, new Person { Name = "n2" }, new Person { Name = "n3" }, new Person { Name = "n4" }, }; var person = people.AsQueryable().Single(p => p.Name == "n1"); // p => p.Name == "n1" var pExp = Expression.Parameter(typeof(Person), "p"); // p var pDotNameExp = Expression.Property(pExp, "Name"); // p.Name var valueExp = Expression.Constant("n1"); // "n1" var pDotNameEqualValueExp = Expression.Equal(pDotName, value); // p.Name == "n1" var lambda = (Expression<Func<Person, bool>>)Expression.Lambda(pDotNameEqualValueExp, new ParameterExpression[] { pExp }); // p => p.Name == "n1" var lambdaDelegate = lambda.Compile(); // 把表达式树 compile 成可执行的委托 var person2 = people.AsQueryable().Single(lambda); // queryable 情况下参数是表达式, 因为要翻译成 SQL var person3 = people.Single(lambdaDelegate); // LINQ 的情况下参数是委托, 因为它是直接执行的 } }
需要动态创建的表达式是这一句 p => p.Name == "n1"
从上面可以看到, 它是逐个逐个通过 Expression 创建和拼接出来的. 看注释一句一句理解.
你会发现它的语法有一种 left right 的感觉, 然后拼着拼着就有点像棵树了, 二叉树
通常, 最后会把 Expression 变成 lambda 作为方法的参数, 需不需要 compile 就看最终拿来干什么. 如果是 Queryable 就直接给表达式树它翻译, 如果是 LINQ 就 compile 成委托执行.
多一个例子
var people = new List<Person> { new Person { Age = 1 }, new Person { Age = 2 }, new Person { Age = 3 }, new Person { Age = 4 }, }; var person = people.AsQueryable().Where(p => p.Age >= 1 && p.Age <= 3); // p => p.Age >= 1 && p.Age <= 3 var pExp = Expression.Parameter(typeof(Person), "p"); // p var pDotAgeExp = Expression.Property(pExp, "Age"); // p.Age var value1Exp = Expression.Constant(1); // 1 var pDotAgeGreatThanOrEqualvalue1Exp = Expression.GreaterThanOrEqual(pDotAgeExp, value1Exp); // p.Age >= 1 var value3Exp = Expression.Constant(3); // 3 var pDotAgeLessThanOrEqualvalue3Exp = Expression.LessThanOrEqual(pDotAgeExp, value3Exp); // p.Age <= 1 var and = Expression.And(pDotAgeGreatThanOrEqualvalue1Exp, pDotAgeLessThanOrEqualvalue3Exp); // p.Age >= 1 && p.Age <= 3 var lambda = (Expression<Func<Person, bool>>)Expression.Lambda(and, pExp); // p => p.Age >= 1 && p.Age <= 3 var lambdaDelegate = lambda.Compile(); var result = people.Where(lambdaDelegate).ToList(); // [1,2,3]
不管表达式如何复杂, 它就是逐个逐个去拼接, 所以不要害怕. 里面有非常非常都多方法, 几乎你能写出来的 code 都能找到对应的 Expression
Expression.Call (表达式调用)
表达式中也可以表达方法调用哦. 当然这个对翻译 SQL 的话可能会有点问题啦. 但是 for 执行的话就很好用哦.
public class Person { public int Age { get; set; } public bool IsDeleted(int value) { return true; } } public class Program { public static void Main() { var people = new List<Person> { new Person { Age = 1 }, new Person { Age = 2 }, new Person { Age = 3 }, new Person { Age = 4 }, }; // p => p.IsDeleted(5) var pExp = Expression.Parameter(typeof(Person), "p"); // p var method = typeof(Person).GetMethod("IsDeleted")!; // 通过反射获取 MethodInfo var callMethodExp = Expression.Call(pExp, method, new Expression[] { Expression.Constant(5) }); // p.IsDeleted(5) var lambda = (Expression<Func<Person, bool>>)Expression.Lambda(callMethodExp, pExp); // p => p.IsDeleted(5) var lambdaDelegate = lambda.Compile(); var result = people.Where(lambdaDelegate).ToList(); // [1,2,3,4] } }
总结
动态创建表达式树, 可以帮助我们更好的管理 EF.Core 的代码. OData 也是通过这个方式去写入 EF Core 的 OData -> EF Core -> SQL.
只要把小方法写好, 一点一点拼接上来, 它只是代码多, 但是并不会乱. 所以不要怕

浙公网安备 33010602011771号