表达式树练习实践: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();
相关文章
|
5天前
|
Java 物联网 C#
C#/.NET/.NET Core学习路线集合,学习不迷路!
C#/.NET/.NET Core学习路线集合,学习不迷路!
|
2月前
|
存储 C# 索引
C# 一分钟浅谈:数组与集合类的基本操作
【9月更文挑战第1天】本文详细介绍了C#中数组和集合类的基本操作,包括创建、访问、遍历及常见问题的解决方法。数组适用于固定长度的数据存储,而集合类如`List<T>`则提供了动态扩展的能力。文章通过示例代码展示了如何处理索引越界、数组长度不可变及集合容量不足等问题,并提供了解决方案。掌握这些基础知识可使程序更加高效和清晰。
81 2
|
3月前
|
开发框架 安全 .NET
全面掌握C#中的类型转换:详解与实践
【8月更文挑战第20天】
137 0
|
22天前
|
测试技术 C# 数据库
C# 一分钟浅谈:测试驱动开发 (TDD) 实践
【10月更文挑战第18天】测试驱动开发(TDD)是一种软件开发方法论,强调先编写测试代码再编写功能代码,以确保代码质量和可维护性。本文从 TDD 的基本概念入手,详细介绍了其核心步骤——编写测试、运行测试并失败、编写代码使测试通过,以及“红绿重构”循环。文章还探讨了 TDD 的优势,包括提高代码质量、促进设计思考、减少调试时间和文档化。此外,文中分析了常见问题及解决方案,如测试覆盖率不足、测试代码过于复杂、忽视重构和测试依赖过多,并通过一个简单的计算器类的代码案例,展示了 TDD 的实际应用过程。
32 1
|
1月前
|
开发框架 NoSQL MongoDB
C#/.NET/.NET Core开发实战教程集合
C#/.NET/.NET Core开发实战教程集合
|
1月前
|
存储 JSON API
HTTP 请求与响应处理:C#中的实践
【10月更文挑战第4天】在现代Web开发中,HTTP协议至关重要,无论构建Web应用还是API开发,都需要熟练掌握HTTP请求与响应处理。本文从C#角度出发,介绍HTTP基础知识,包括请求与响应结构,并通过`HttpClient`库演示如何发送GET请求及处理响应,同时分析常见错误并提供解决方案,助你更高效地完成HTTP相关任务。
83 2
|
1月前
|
数据采集 C# 数据库
数据验证与错误处理:C#中的实践
【10月更文挑战第1天】在软件开发中,数据验证与错误处理至关重要,不仅能提升程序的健壮性和安全性,还能改善用户体验。本文从基础概念入手,详细介绍了C#中的数据验证方法,包括使用自定义属性和静态方法验证数据,以及常见的错误处理技巧,如Try-Catch-Finally结构和自定义异常。通过具体示例,帮助读者掌握最佳实践,构建高质量应用。
83 3
|
1月前
|
开发框架 缓存 算法
开源且实用的C#/.NET编程技巧练习宝库(学习,工作,实践干货)
开源且实用的C#/.NET编程技巧练习宝库(学习,工作,实践干货)
|
2月前
|
安全 程序员 编译器
C#一分钟浅谈:泛型编程基础
在现代软件开发中,泛型编程是一项关键技能,它使开发者能够编写类型安全且可重用的代码。C# 自 2.0 版本起支持泛型编程,本文将从基础概念入手,逐步深入探讨 C# 中的泛型,并通过具体实例帮助理解常见问题及其解决方法。泛型通过类型参数替代具体类型,提高了代码复用性和类型安全性,减少了运行时性能开销。文章详细介绍了如何定义泛型类和方法,并讨论了常见的易错点及解决方案,帮助读者更好地掌握这一技术。
74 11
|
2月前
|
SQL 开发框架 安全
并发集合与任务并行库:C#中的高效编程实践
在现代软件开发中,多核处理器普及使多线程编程成为提升性能的关键。然而,传统同步模型在高并发下易引发死锁等问题。为此,.NET Framework引入了任务并行库(TPL)和并发集合,简化并发编程并增强代码可维护性。并发集合允许多线程安全访问,如`ConcurrentQueue&lt;T&gt;`和`ConcurrentDictionary&lt;TKey, TValue&gt;`,有效避免数据不一致。TPL则通过`Task`类实现异步操作,提高开发效率。正确使用这些工具可显著提升程序性能,但也需注意任务取消和异常处理等常见问题。
48 1