欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-05-28 21:17:16
表达式树练习实践:C 值类型、引用类型、泛型、集合、调用函数 [TOC] 一,定义变量 C 表达式树中,定义一个变量,使用 。 创建变量结点的方法有两种, 两种方式都是生成 类型 和 都具有两个重载。他们创建一个 ParameterExpression节点,该节点可用于标识表达式树中的参数或变量。 ......

表达式树练习实践: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();