ASP.NET Core C# 反射 & 表达式树 (第一篇)
前言
以前就写过几篇关于反射和表达式树的学习笔记, 但是写的很乱. 最近常用到反射和表达式树, 所以特别写一篇做一个整理吧.
反射在项目中会用到的地方, 一般不是因为要实现业务逻辑, 更多的是因为要更好的代码管理. 这个动机很重要.
这篇不会讲原理, 主要是整理出常用到的方式. 会从简单和常用到的慢慢讲到复杂的.
Get/Set Property Value
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(); // step 1. 从 object 获取到 Type
var property = type.GetProperty("Name")!; // step 2. 通过 Type + propertyName (string) 获取到 PropertyInfo
var name = (string)property.GetValue(person, null)!; // step 3. 通过 PropertyInfo 的读方法, 获取对象属性值
Console.WriteLine(name);
property.SetValue(person, "Derrick"); // step 3. 通过 PropertyInfo 的写方法, 写入对象属性值
Console.WriteLine(person.Name);
}
}
流程大概就是:
1. 先反射出对象类型
2. 再反射出属性操作器 (依据 string)
3. 通过操作器, 把 object + object 类型属性操作器 + 值, 搞在一起, 最后就修改掉了对象属性值.
如果只看这一个需求的话, 需要这么多步骤才能做到这点事情, 确实很烦. 但是如果站在一个类型语言要让它"动态"的话, 它的这个步骤就可以做到非常多事情了. 所以我们接着往下.
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);
}
}
找出方法执行器, 然后调用就可以了.
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 是一定要放的哦, 不放会直接报错的.
Return Value
public string GetName()
{
return Name;
}
var name = (string)method.Invoke(person, Array.Empty<string>())!;
关键就是要强转, 不然返回都是 object 类型.
Async Task
public async Task DoSomethingAsync()
{
await Task.Delay(2000);
}
await (Task)method.Invoke(person, Array.Empty<string>())!;
关键依然是强转就可以了.
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> 哦)
var name = await (dynamic)method.Invoke(person, Array.Empty<string>())!;
if (name is string)
{
Console.WriteLine("name is string");
}
Anonymous method / Delegate
匿名函数, 通常出现在一个方法作用域内.
public static async Task Main()
{async Task<string> GetNameAsync(string value) {
await Task.Delay(2000);
return "test";
}
}
直接装进 variable 调用
var @delegate = GetNameAsync; // 把函数装进变量中
var returnValue = await @delegate("value"); // 直接调用变量. 因为类型推导所以完全等价于 await GetNameAsync("value")
把 var 改成 Delegate 类型, 就需要换一个方式调用
Delegate @delegate = GetNameAsync;
var returnValue = await (Task<string>)@delegate.Method.Invoke(@delegate.Target, new object[] { "value" })!;
因为没有类型推导, 所以需要很多强转.
另外, 需要用 delegate.Method 和 delegate.Target 来调用这个方法.
委托方法和对象是无关的, 所以 Invoke 的时候传入的是 Target 而不是 Object.

比如在类里面有一个委托, 它内部是调用不到 this 的哦.
我们可以通过 get property value 获取到这个委托, 然后通过 delegate.Method 来调用, 它的 target 依然是 delegate.Target 而不是 object.
Tips: DynamicInvoke 调用 (推荐)
var returnValue = await (Task<string>)@delegate.DynamicInvoke(new object[] { "value" })!;
这个更好用, 效果和上面一样(没有 100% 测试). 但语法更简单.
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);
}
}
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 哦.
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);
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);
}
}
小总结
可以看得出来, 整个反射的套路就那几招.
找到类型, 获取一些操作器, 然后传入 object, 返回 object, 然后强转.
反射就是撇开了静态类型, 让东西都变成 object, 然后靠你自己去使用它, 如果不习惯写动态语言的人, 就容易遇到 run time error.
下一篇, 我们主要来看看找类型这个环节.
ASP.NET Core C# 反射 & 表达式树 (第一篇)的更多相关文章
- ASP.NET Core中使用表达式树创建URL
当我们在ASP.NET Core中生成一个action的url会这样写: var url=_urlHelper.Action("Index", "Home"); ...
- 使用Asp.Net Core MVC 开发项目实践[第一篇:项目结构说明]
先从下图看整体项目结构: Mango.Manager: 为后台管理项目 Mango.Web: 为前台项目 Mango.Framework.Core: 为常用的基础操作类项目 Mango.Framewo ...
- Pro ASP.NET Core MVC 第6版 第一章
目录 第一章 ASP.NET Core MVC 的前世今生 ASP.NET Core MVC 是一个微软公司开发的Web应用程序开发框架,它结合了MVC架构的高效性和简洁性,敏捷开发的思想和技术和.N ...
- C# 反射 表达式树 模糊搜索
反射实体T,非datetime字段反射获取表达式树 public static Expression<Func<T, bool>> GetSearchExpression& ...
- ASP.NET Core管道深度剖析[共4篇]
之所以称ASP.NET Core是一个Web开发平台,源于它具有一个极具扩展性的请求处理管道,我们可以通过这个管道的定制来满足各种场景下的HTTP处理需求.ASP. NET Core应用的很多特性,比 ...
- 了解ASP.NET Core 依赖注入,看这篇就够了 于2017年11月6日由jesseliu发布
DI在.NET Core里面被提到了一个非常重要的位置, 这篇文章主要再给大家普及一下关于依赖注入的概念,身边有工作六七年的同事还个东西搞不清楚.另外再介绍一下.NET Core的DI实现以及对实例 ...
- 了解ASP.NET Core 依赖注入,看这篇就够了
DI在.NET Core里面被提到了一个非常重要的位置, 这篇文章主要再给大家普及一下关于依赖注入的概念,身边有工作六七年的同事还个东西搞不清楚.另外再介绍一下.NET Core的DI实现以及对实例 ...
- 任务21 :了解ASP.NET Core 依赖注入,看这篇就够了
DI在.NET Core里面被提到了一个非常重要的位置, 这篇文章主要再给大家普及一下关于依赖注入的概念,身边有工作六七年的同事还个东西搞不清楚.另外再介绍一下.NET Core的DI实现以及对实例 ...
- 你必须知道ASP.NET知识------从IIS到httpmodule(第一篇)
一.写在前面 最近有时间,顺便将这系列洗完,接着上文:IIS各个版本知识总结 这篇文章原本计划写到HttpHandler为止,但限于篇幅就写到httpmodule 本文有不足之处,求指正,希望我能将它 ...
- 使用Asp.Net Core MVC 开发项目实践[第二篇:EF Core]
在项目中使用EF Core还是比较容易的,在这里我们使用的版本是EF Core 2.2. 1.使用nuget获取EF Core包 这个示例项目使用的是SQLSERVER,所以还需要下载Microsof ...
随机推荐
- webpack4.15.1 学习笔记(四) — Tree shaking
目录 Tree shaking 原理 标记效果 副作用代码不可被删除 如何实现 Tree shaking 的几种方法 总结 Tree shaking 本质上为了消除无用的js代码,减少加载文件体积的方 ...
- 超级炫酷的终端神器 eDEX-UI
目录 eDEX-UI主要亮点: 优点: 软件简介 安装 Linux Windows 效果 更换皮肤 matrix Tron-disrupted 退出 常见问题解答 eDEX-UI,不仅是一款全屏幕.跨 ...
- [oeasy]python0132_变量含义_meaning_声明_declaration_赋值_assignment
变量定义 回忆上次内容 上次回顾了一下历史 python 是如何从无到有的 看到 Guido 长期的坚持和努力 编程语言的基础都是变量声明 python是如何声明变量的呢? 变量 想要定义变量 ...
- 基础-数组_C语言
C 语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合.数组是用来存储一系列数据,但它往往被认为是一系列相同类型的变量. 数组的声明并不是声明一个个单独的变量,比如 runoob0. ...
- Odoo 基于Win10搭建基于Win10搭建odoo14开发环境搭建
实践环境 win10 Python 3.6.2 odoo_14.0.latest.tar.gz 下载地址: https://download.odoocdn.com/download/14/src?p ...
- Oracle 序列学习与使用总结
Oracle序列学习与使用总结 by:授客 QQ:1033553122 简述 序列是oracle提供的用于生成一系列数字的数据库对象,序列会自动生成顺序递增的序列号,可用于提供唯一的自动递增主键.序列 ...
- vue pinia sessionstorage 数据存储不上的原因
vue pinia sessionstorage 的坑 默认的配置是开始 localStorage 如果用 sessionstorage 则发现数据存储不上 ,是因为缺少了序列化和反序列化 impor ...
- 对比python学julia(第三章:游戏编程)--(第三节)疯狂摩托(3)
3.3. 编程实现 2. 控制摩托车和箱子 在这个步骤中,将编程控制摩托车和箱子角色的运动,让摩托车在沙漠公路上能够加速或减速行驶,在碰到箱子时能够停止,以及显示麾托车的行驶速度和里程等. ( ...
- 【Java-GUI】07 Swing01 入门案例
Swing是Java自己开发出的一套GUI组件,不同于AWT去调用操作系统的GUI 正是因为非系统平台的GUI,所以程序运行的要慢一些 涉及的设计模式:MVC模式 Model(组件对象状态) View ...
- 【Eclipse】入门使用
Eclipse界面简单概述 第一次启动时,工作空间的选择 工作界面的介绍: 选项条 工具栏 工程浏览窗口 工程大纲窗口 控制台输出窗口 在窗口选项中悬浮放在Show View选项中可以查看所有的窗口 ...