表达式树练习实践:C#值类型、引用类型、泛型、集合、调用函数
表达式树练习实践:C#值类型、引用类型、泛型、集合、调用函数

一,定义变量
C# 表达式树中,定义一个变量,使用 ParameterExpression。
创建变量结点的方法有两种,
Expression.Parameter()
Expression.Variable()
// 另外,定义一个常量可以使用 Expression.Constant()。
两种方式都是生成 ParameterExpression 类型 Parameter() 和 Variable() 都具有两个重载。他们创建一个 ParameterExpression节点,该节点可用于标识表达式树中的参数或变量。
对于使用定义:
Expression.Variable 用于在块内声明局部变量。
Expression.Parameter用于声明输入值的参数。
先看第一种
public static ParameterExpression Parameter(Type type)
{
return Parameter(type, name: null);
}
public static ParameterExpression Variable(Type type)
{
return Variable(type, name: null);
}
从代码来看,没有区别。
再看看具有两个参数的重载
public static ParameterExpression Parameter(Type type, string name)
{
Validate(type, allowByRef: true);
bool byref = type.IsByRef;
if (byref)
{
type = type.GetElementType();
}
return ParameterExpression.Make(type, name, byref);
}
public static ParameterExpression Variable(Type type, string name)
{
Validate(type, allowByRef: false);
return ParameterExpression.Make(type, name, isByRef: false);
}
如你所见,两者只有一个 allowByRef 出现了区别,Paramter 允许 Ref, Variable 不允许。
笔者在官方文档和其他作者文章上,都没有找到具体区别是啥,去 stackoverflow 搜索和查看源代码后,确定他们的区别在于 Variable 不能使用 ref 类型。
从字面意思来看,声明一个变量,应该用Expression.Variable, 函数的传入参数应该使用Expression.Parameter。
无论值类型还是引用类型,都是这样子定义。
二,访问变量/类型的属性字段和方法
访问变量或类型的属性,使用
Expression.Property()
访问变量/类型的属性或字段,使用
Expression.PropertyOrField()
访问变量或类型的方法,使用
Expression.Call()
访问属性字段和方法
Expression.MakeMemberAccess
他们都返回一个 MemberExpression类型。
使用上,根据实例化/不实例化,有个小区别,上面说了变量或类型。
意思是,已经定义的值类型或实例化的引用类型,是变量;
类型,就是指引用类型,不需要实例化的静态类型或者静态属性字段/方法。
上面的解释不太严谨,下面示例会慢慢解释。
1. 访问属性
使用 Expression.Property() 或 Expression.PropertyOrField()调用属性。
调用静态类型属性
Console 是一个静态类型,Console.Title 可以获取编译器程序的实际位置。
Console.WriteLine(Console.Title);
使用表达式树表达如下
MemberExpression member = Expression.Property(null, typeof(Console).GetProperty("Title"));
Expression<Func<string>> lambda = Expression.Lambda<Func<string>>(member);
string result = lambda.Compile()();
Console.WriteLine(result);
Console.ReadKey();
因为调用的是静态类型的属性,所以第一个参数为空。
第二个参数是一个 PropertyInfo 类型。
调用实例属性/字段
C#代码如下
List<int> a = new List<int>() { 1, 2, 3 };
int result = a.Count;
Console.WriteLine(result);
Console.ReadKey();
在表达式树,调用实例的属性
ParameterExpression a = Expression.Parameter(typeof(List<int>), "a");
MemberExpression member = Expression.Property(a, "Count");
Expression<Func<List<int>, int>> lambda = Expression.Lambda<Func<List<int>, int>>(member, a);
int result = lambda.Compile()(new List<int> { 1, 2, 3 });
Console.WriteLine(result);
Console.ReadKey();
除了 Expression.Property() ,其他的方式请自行测试,这里不再赘述。
2. 调用函数
使用 Expression.Call() 可以调用一个静态类型的函数或者实例的函数。
调用静态类型的函数
以 Console 为例,调用 WriteLine() 方法
Console.WriteLine("调用WriteLine方法");
MethodCallExpression method = Expression.Call(
null,
typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) }),
Expression.Constant("调用WriteLine方法"));
Expression<Action> lambda = Expression.Lambda<Action>(method);
lambda.Compile()();
Console.ReadKey();
Expression.Call() 的重载方法比较多,常用的重载方法是
public static MethodCallExpression Call(Expression instance, MethodInfo method, params Expression[] arguments)
因为要调用静态类型的函数,所以第一个 instance 为空(instance英文意思是实例)。
第二个 method 是要调用的重载方法。
最后一个 arguments 是传入的参数。
调用实例的函数
写一个类
public class Test
{
public void Print(string info)
{
Console.WriteLine(info);
}
}
调用实例的 Printf() 方法
Test test = new Test();
test.Print("打印出来");
Console.ReadKey();
表达式表达如下
ParameterExpression a = Expression.Variable(typeof(Test), "test");
MethodCallExpression method = Expression.Call(
a,
typeof(Test).GetMethod("Print", new Type[] { typeof(string) }),
Expression.Constant("打印出来")
);
Expression<Action<Test>> lambda = Expression.Lambda<Action<Test>>(method,a);
lambda.Compile()(new Test());
Console.ReadKey();
注意的是,Expression.Variable(typeof(Test), "test"); 仅定义了一个变量,还没有初始化/赋值。对于引用类型来说,需要实例化。
上面的方式,是通过外界实例化传入里面的,后面会说如何在表达式内实例化。
三,实例化引用类型
引用类型的实例化,使用 new ,然后选择调用合适的构造函数、设置属性的值。
那么,根据上面的步骤,我们分开讨论。
new
使用 Expression.New()来调用一个类型的构造函数。
他有五个重载,有两种常用重载:
public static NewExpression New(ConstructorInfo constructor);
public static NewExpression New(Type type);
依然使用上面的 Test 类型
NewExpression newA = Expression.New(typeof(Test));
默认没有参数的构造函数,或者只有一个构造函数,像上面这样调用。
如果像指定一个构造函数,可以
NewExpression newA = Expression.New(typeof(Test).GetConstructor(xxxxxx));
这里就不详细说了。
给属性赋值
实例化一个构造函数的同时,可以给属性赋值。
public static MemberInitExpression MemberInit(NewExpression newExpression, IEnumerable<MemberBinding> bindings);
public static MemberInitExpression MemberInit(NewExpression newExpression, params MemberBinding[] bindings);
两种重载是一样的。
我们将 Test 类改成
public class Test
{
public int sample { get; set; }
public void Print(string info)
{
Console.WriteLine(info);
}
}
然后
var binding = Expression.Bind(
typeof(Test).GetMember("sample")[0],
Expression.Constant(10)
);
创建引用类型
Expression.MemberInit()
表示调用构造函数并初始化新对象的一个或多个成员。
如果实例化一个类,可以使用
NewExpression newA = Expression.New(typeof(Test));
MemberInitExpression test = Expression.MemberInit(newA,
new List<MemberBinding>() { }
);
如果要在实例化时给成员赋值
NewExpression newA = Expression.New(typeof(Test));
// 给 Test 类型的一个成员赋值
var binding = Expression.Bind(
typeof(Test).GetMember("sample")[0],Expression.Constant(10));
MemberInitExpression test = Expression.MemberInit(newA,
new List<MemberBinding>() { binding}
);
示例
实例化一个类型,调用构造函数、给成员赋值,示例代码如下
// 调用构造函数
NewExpression newA = Expression.New(typeof(Test));
// 给 Test 类型的一个成员赋值
var binding = Expression.Bind(
typeof(Test).GetMember("sample")[0], Expression.Constant(10));
// 实例化一个类型
MemberInitExpression test = Expression.MemberInit(newA,
new List<MemberBinding>() { binding }
);
// 调用方法
MethodCallExpression method1 = Expression.Call(
test,
typeof(Test).GetMethod("Print", new Type[] { typeof(string) }),
Expression.Constant("打印出来")
);
// 调用属性
MemberExpression method2 = Expression.Property(test, "sample");
Expression<Action> lambda1 = Expression.Lambda<Action>(method1);
lambda1.Compile()();
Expression<Func<int>> lambda2 = Expression.Lambda<Func<int>>(method2);
int sample = lambda2.Compile()();
Console.WriteLine(sample);
Console.ReadKey();
四,实例化泛型类型于调用
将 Test 类,改成这样
public class Test<T>
{
public void Print<T>(T info)
{
Console.WriteLine(info);
}
}
Test 类已经是一个泛型类,表达式实例化示例
static void Main(string[] args)
{
RunExpression<string>();
Console.ReadKey();
}
public static void RunExpression<T>()
{
// 调用构造函数
NewExpression newA = Expression.New(typeof(Test<T>));
// 实例化一个类型
MemberInitExpression test = Expression.MemberInit(newA,
new List<MemberBinding>() { }
);
// 调用方法
MethodCallExpression method = Expression.Call(
test,
typeof(Test<T>).GetMethod("Print").MakeGenericMethod(new Type[] { typeof(T) }),
Expression.Constant("打印出来")
);
Expression<Action> lambda1 = Expression.Lambda<Action>(method);
lambda1.Compile()();
Console.ReadKey();
}
五,定义集合变量、初始化、添加元素
集合类型使用 ListInitExpression表示。
创建集合类型,需要使用到
ElementInit 表示 IEnumerable集合的单个元素的初始值设定项。
ListInit 初始化一个集合。
C# 中,集合都实现了 IEnumerable,集合都具有 Add 扥方法或属性。
使用 C# 初始化一个集合并且添加元素,可以这样
List<string> list = new List<string>()
{
"a",
"b"
};
list.Add("666");
而在表达式树里面,是通过 ElementInit 调用 Add 方法初始化/添加元素的。
示例
MethodInfo listAdd = typeof(List<string>).GetMethod("Add");
/*
* new List<string>()
* {
* "a",
* "b"
* };
*/
ElementInit add1 = Expression.ElementInit(
listAdd,
Expression.Constant("a"),
Expression.Constant("b")
);
// Add("666")
ElementInit add2 = Expression.ElementInit(listAdd, Expression.Constant("666"));
示例
MethodInfo listAdd = typeof(List<string>).GetMethod("Add");
ElementInit add1 = Expression.ElementInit(listAdd, Expression.Constant("a"));
ElementInit add2 = Expression.ElementInit(listAdd, Expression.Constant("b"));
ElementInit add3 = Expression.ElementInit(listAdd, Expression.Constant("666"));
NewExpression list = Expression.New(typeof(List<string>));
// 初始化值
ListInitExpression setList = Expression.ListInit(
list,
add1,
add2,
add3
);
// 没啥执行的,就这样看看输出的信息
Console.WriteLine(setList.ToString());
MemberExpression member = Expression.Property(setList, "Count");
Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(member);
int result = lambda.Compile()();
Console.WriteLine(result);
Console.ReadKey();
表达式树练习实践:C#值类型、引用类型、泛型、集合、调用函数的更多相关文章
- 表达式树练习实践:C# 五类运算符的表达式树表达
目录 表达式树练习实践:C# 运算符 一,算术运算符 + 与 Add() - 与 Subtract() 乘除.取模 自增自减 二,关系运算符 ==.!=.>.<.>=.<= 三 ...
- 表达式树练习实践:C#判断语句
目录 表达式树练习实践:C#判断语句 if if...else switch ?? 和 ?: 表达式树练习实践:C#判断语句 判断语句 C# 提供了以下类型的判断语句: 语句 描述 if 一个 if ...
- 表达式树练习实践:C# 循环与循环控制
目录 表达式树练习实践:C# 循环 LabelTarget for / while 循环 无限循环 最简单的循环 多次循环 break 和 continue 一起 表达式树练习实践:C# 循环 C# ...
- 30天C#基础巩固-----值类型/引用类型,泛型,空合并操作符(??),匿名方法
一:值类型/引用类型的区别 值类型主要包括简单类型,枚举类型,和结构体类型等,值类型的实例通常被分配在线程堆栈上面变量保存的内容是实例数据本身.引用类型被分配在托管堆上,变量保存的是地址.引 ...
- 数往知来C#之接口 值类型与引用类型 静态非静态 异常处理 GC垃圾回收 值类型引用类型内存分配<四>
C# 基础接口篇 一.多态复习 使用个new来实现,使用virtual与override -->new隐藏父类方法 根据当前类型,电泳对应的方法(成员) -->override ...
- 对比两个同类型的泛型集合并返回差异泛型集合 ——两个List<类名>的比较
1: /// <summary> 2: /// 对比两个同类型的泛型集合并返回差异泛型集合 3: /// </summary> 4: /// <typeparam nam ...
- 【C# 表达式树 三】ExpressionType 节点类型种类
// // 摘要: // 描述表达式目录树的节点的节点类型. public enum ExpressionType { // // 摘要: // 加法运算,如 a + b,针对数值操作数,不进行溢出检 ...
- 深入C#内存管理来分析值类型&引用类型,装箱&拆箱,堆栈几个概念组合之间的区别
C#初学者经常被问的几道辨析题,值类型与引用类型,装箱与拆箱,堆栈,这几个概念组合之间区别,看完此篇应该可以解惑. 俗话说,用思想编程的是文艺程序猿,用经验编程的是普通程序猿,用复制粘贴编程的是2B程 ...
- 6个重要的.NET概念:栈,堆,值类型,引用类型,装箱,拆箱
引言 本篇文章主要介绍.NET中6个重要的概念:栈,堆,值类型,引用类型,装箱,拆箱.文章开始介绍当你声明一个变量时,编译器内部发生了什么,然后介绍两个重要的概念:栈和堆:最后介绍值类型和引用类型,并 ...
随机推荐
- C# 复制Excel单元格格式
本文将介绍通过C# 复制Excel单元格格式的方法,包括复制单元格中的字体.字号.字体加粗.倾斜.单元格背景色.字体颜色.单元格数字格式.单元格文字方向.文字旋转.下划线.单元格对齐方式.单元格边框等 ...
- 【JS档案揭秘】第四集 关于this的讨论到此为止
网上关于this的指向问题的博客文章很多,但大多数都是复制粘贴,也不能用简洁的语言讲清楚,而是不停地写一些示例,看得人云里雾里. 这一集,我只给出结论,以及判定的通用方法,至于是否确实如我所讲,大家可 ...
- 2019Hexo博客Next主题深度美化 打造一个炫酷博客(2)-奥怪的小栈
219/8/1 更新 本文转载于:奥怪的小栈 这篇文章告诉你在搭建好博客后,面对网上千篇一律的美化教程怎么才能添加自己独特点,使人眼前一亮. 本站基于HEXO+Github搭建. 所以你需要准备好HE ...
- idea中pom如何加载jar包依赖
1.需求分析 在特定需求的情况下,idea需要加载jar包,那么如何在idea中正确的配置jar依赖呢?今天博主就这个问题给大伙讲解下,希望对大伙有所帮助 2.实现方案①在工程src目录下新建l ...
- webservice接口和http接口(API接口)的区别
web service(SOAP)与HTTP接口的区别: 什么是web service? 答:soap请求是HTTP POST的一个专用版本,遵循一种特殊的xml消息格式Content- ...
- Java和Tomcat安装教程
jdk安装与配置1.下载好对应的jdk2.安装JDK 直接运行exe可执行程序,默认安装即可:备注:路径可以选其他盘符,路径要全部为英文. 3.配置环境变量 新建变量名:JAVA_HOME,变量值:D ...
- python循环语句的一些题型
1. 使用while循环输出1 2 3 4 5 6 8 9 10 i =1 while i <= 10: print(i,end=' ') i = i +1 if i == 7: i = i + ...
- Linux中JDK安装配置
安装jdk 1)下载地址:https://www.oracle.com/technetwork/java/javase/downloads/index.html 我选择jdk1.8版本 2)上传至服务 ...
- net start mysql提示:服务名无效
1.win+R打开运行窗口,输入services.msc 2.在其中查看mysql的服务名,我的是MySQL55 3.以管理员身份打开cmd,输入net start MySQL55 出现下图,代表my ...
- HDU 4396More lumber is required 过至少K条边的最短路
/* ** 题目要求过最少k条边的最短路 */ #include <iostream> #include <cstdio> #include <cstring> # ...