表达式树练习实践: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 不允许。
笔者在官方文档和其他作者文章上,都没有找到具体区别是啥,去 * 搜索和查看源代码后,确定他们的区别在于 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();