c#编程细节(三)linq与表达式目录树

linq

构建一个Linq,需要lambam表达式,匿名类,扩展方法
lambam表达式
NoReturnWithPara method = (x, y) => Console.WriteLine(“This is DoNothing6”);
lambda表达式是个什么呢?
//只是一个方法(作用)
//实际上是一个类中类,里面的一个internal方法,然后被绑定到静态的委托类型字段
在一个多播委托加入这个lambam表达式,是删不掉的
扩展方法
静态类里面的静态方法,第一个参数类型前面加上this
/// 用途:可以不修改类,增加方法;也就是方便一点
/// 缺陷:优先调用类型的实例方法(有隐患)
/// 扩展基类型,导致任何子类都有这个方法,而且还可能被覆盖啥的
///
/// 指定类型扩展,不要对基类型 否则成本太高
///
/// 没有扩展属性
例:public static void Sing(this Student student)
{
Console.WriteLine($"{student.Name} Sing a Song");
}
这样就可以直接 Student.Sing
例2:

`/// <summary>
        /// 1 基于委托封装解耦,去掉重复代码,完成通用代码
        /// 2 泛型,应对各种数据类型
        /// 3 加迭代器,按需获取 就是只有在真的遍历时才会
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="source"></param>
        /// <param name="func"></param>
        /// <returns></returns>
        //public static List<T> ElevenWhere<T>(this List<T> source, Func<T, bool> func)
        //{
        //    var list = new List<T>();
        //    foreach (var item in source)
        //    {
        //        if (func.Invoke(item))
        //        {
        //            list.Add(item);
        //        }
        //    }
        //    return list;
        //}
        public static IEnumerable<T> ElevenWhere<T>(this IEnumerable<T> source, Func<T, bool> func)
        {
            if (source == null)
            {
                throw new Exception("source is null");
            }
            if (func == null)
            {
                throw new Exception("func is null");
            }

            foreach (var item in source)
            {
                if (func.Invoke(item))
                {
                    yield return item;
                }
            }
        }


var result = list.ElevenWhere<Student>(s =>
                    {
                        Console.WriteLine("检查数据是否满足条件");
                        Thread.Sleep(100);
                        return s.Age < 30;
                    });//陈述式语法

                    foreach (var item in result)
                    {

                    }

`

他会在foreach的时候触发进行

而一个linq语句的构成就是通过这些
var linq = list
.Where(s =>
{
Console.WriteLine(“检查数据是否满足条件”);
Thread.Sleep(100);
return s.Age < 30;
})
//.Select<Student, int>(s => s.Name.Length);
.Select(s => new
{
Id = s.Id,
Name = s.Name,
Length = s.Name.Length
});
转到定义里
public static IEnumerable Where(this IEnumerable source, Func<TSource, bool> predicate);

表达式目录树

Func<int, int, int> func = (m, n) => m * n + 2;// new Func<int, int, int>((m, n) => m * n + 2);
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;//lambda表达式声明表达式目录树
//表达式目录树:语法树,或者说是一种数据结构;可以被我们解析(学完数据结构也不知道在哪里可以用到,终于在这里看到使用的一个实例)
委托中的lambda表达式本质是方法,只能被执行,但表达式目录树可以被一层层解析
像linq to sql中的委托参数都是包着一层表达式目录树,这样才可以被解析成sql语句
int iResult1 = func.Invoke(12, 23);
int iResult2 = exp.Compile().Invoke(12, 23);//可以转换过去

经过反编译(m, n) => m * n + 2其实是这样声明出来的

                ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
                ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
                var multiply = Expression.Multiply(parameterExpression, parameterExpression2);
                var constant = Expression.Constant(2, typeof(int));
                var add = Expression.Add(multiply, constant);

                Expression<Func<int, int, int>> expression =
                    Expression.Lambda<Func<int, int, int>>(
                        add,
                        new ParameterExpression[]
                        {
                             parameterExpression,
                             parameterExpression2
                        });

表达式目录树可以被分解,因此它是可以被拼接的,拼接后.Compile()就可以变成一个委托,所以是非常有意义的
例一,

//拼装表达式目录树
                //Expression<Func<People, bool>> lambda = x => x.Age > 5;
                ParameterExpression parameterExpression = Expression.Parameter(typeof(People), "x");
                Expression propertyExpression = Expression.Property(parameterExpression, typeof(People).GetProperty("Age"));
                //Expression property = Expression.Field(parameterExpression, typeof(People).GetField("Id"));
                ConstantExpression constantExpression = Expression.Constant(5, typeof(int));
                BinaryExpression binary = Expression.GreaterThan(propertyExpression, constantExpression);//添加方法的
                Expression<Func<People, bool>> lambda = Expression.Lambda<Func<People, bool>>(binary, new ParameterExpression[]
                {
                    parameterExpression
                });
                bool bResult = lambda.Compile()(new People()
                {
                    Id = 11,
                    Name = "Eleven",
                    Age = 31
                });

也许应用场景有许多其他的需求,比如x => x.Id > 1与其他属性的各种组合,就可以用表达式目录树进行各种组合的拼接

例二,将一个对象转化为另一个对象,或者说一个对象的深拷贝,就可以用到表达式目录树的拼接

 People people = new People()
                {
                    Id = 11,
                    Name = "Eleven",
                    Age = 31
                };
                PeopleCopy peopleCopy = new PeopleCopy()
                {
                    Id = people.Id,
                    Name = people.Name,
                    Age = people.Age
                };

这种硬编码性能上是最好的,但是太不灵活
可以加反射进行改进

public static TOut Trans<TIn, TOut>(TIn tIn)
        {
            TOut tOut = Activator.CreateInstance<TOut>();
            foreach (var itemOut in tOut.GetType().GetProperties())
            {
                var propIn = tIn.GetType().GetProperty(itemOut.Name);
                itemOut.SetValue(tOut, propIn.GetValue(tIn));
            }
            foreach (var itemOut in tOut.GetType().GetFields())
            {
                var fieldIn = tIn.GetType().GetField(itemOut.Name);
                itemOut.SetValue(tOut, fieldIn.GetValue(tIn));
            }
            return tOut;
        }

虽然灵活性大大提高,但是反射的性能比较差

public class SerializeMapper
    {
        /// <summary>
        /// 序列化反序列化方式
        /// </summary>
        /// <typeparam name="TIn"></typeparam>
        /// <typeparam name="TOut"></typeparam>
        public static TOut Trans<TIn, TOut>(TIn tIn)
        {
            return JsonConvert.DeserializeObject<TOut>(JsonConvert.SerializeObject(tIn));
        }
    }

通过序列化反序列化也可以但是性能还不如反射

private static Dictionary<string, object> _Dic = new Dictionary<string, object>();

        /// <summary>
        /// 字典缓存表达式树
        /// </summary>
        /// <typeparam name="TIn"></typeparam>
        /// <typeparam name="TOut"></typeparam>
        /// <param name="tIn"></param>
        /// <returns></returns>
        public static TOut Trans<TIn, TOut>(TIn tIn)
        {
            //字典key为两对象的名字,value为
            string key = string.Format("funckey_{0}_{1}", typeof(TIn).FullName, typeof(TOut).FullName);
            if (!_Dic.ContainsKey(key))
            {
                ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
                List<MemberBinding> memberBindingList = new List<MemberBinding>();
                foreach (var item in typeof(TOut).GetProperties())
                {
                    MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
                    MemberBinding memberBinding = Expression.Bind(item, property);
                    memberBindingList.Add(memberBinding);
                }
                foreach (var item in typeof(TOut).GetFields())
                {
                    MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
                    MemberBinding memberBinding = Expression.Bind(item, property);
                    memberBindingList.Add(memberBinding);
                }
                MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
                Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
                {
                    parameterExpression
                });
                Func<TIn, TOut> func = lambda.Compile();//拼装是一次性的
                _Dic[key] = func;
            }
            return ((Func<TIn, TOut>)_Dic[key]).Invoke(tIn);
        }

这个是拼接后的表达式lambam
//Expression<Func<People, PeopleCopy>> lambda = p =>
// new PeopleCopy()
// {
// Id = p.Id,
// Name = p.Name,
// Age = p.Age
// };
//lambda.Compile()(people);
只需要第一次的初始化,然后会缓存到静态字典中,性能也比较好,这就是动态生成硬编码
再优化的话可以使用泛型缓存

以上是通过反编译得到大概代码,然后自己进行解读拼装,除此之外,c#自带了解析目录树的类库方法
在这里插入图片描述
层层递归visit,将一个表达式二元二元逐层解析出来,识别表达式以及子表达式然后调用不同的虚方法
在这里插入图片描述
现在我们大概知道了这个ExpressionVistor的visit方法的实现
那他到底有什么用呢?
其实如果学会了表达式目录树你可以自己封装一个小orm或者小ef框架
逐步解析的过程中将所需部分替换sql语法例如下面

internal static string ToSqlOperator(this ExpressionType type)
        {
            switch (type)
            {
                case (ExpressionType.AndAlso):
                case (ExpressionType.And):
                    return "AND";
                case (ExpressionType.OrElse):
                case (ExpressionType.Or):
                    return "OR";
                case (ExpressionType.Not):
                    return "NOT";
                case (ExpressionType.NotEqual):
                    return "<>";
                case ExpressionType.GreaterThan:
                    return ">";
                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                case ExpressionType.LessThan:
                    return "<";
                case ExpressionType.LessThanOrEqual:
                    return "<=";
                case (ExpressionType.Equal):
                    return "=";
                default:
                    throw new Exception("不支持该方法");
            }

        }

最后将一段linq表达式解析为sql语句,这就那些orm框架的原理
除此之外,我们还有想要拼接linq的时候可以借助表达式目录树

/// <summary>
    /// 合并表达式 And Or  Not扩展
    /// </summary>
    public static class ExpressionExtend
    {
        /// <summary>
        /// 合并表达式 expr1 AND expr2
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            if (expr1 == null)
                return expr2;
            else if (expr2 == null)
                return expr1;

            //return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, expr2.Body), expr1.Parameters);
            ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
            NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);

            var left = visitor.Replace(expr1.Body);
            var right = visitor.Replace(expr2.Body);
            var body = Expression.And(left, right);
            return Expression.Lambda<Func<T, bool>>(body, newParameter);

        }
        /// <summary>
        /// 合并表达式 expr1 or expr2
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expr1"></param>
        /// <param name="expr2"></param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
        {
            if (expr1 == null)
                return expr2;
            else if (expr2 == null)
                return expr1;

            ParameterExpression newParameter = Expression.Parameter(typeof(T), "c");
            NewExpressionVisitor visitor = new NewExpressionVisitor(newParameter);

            var left = visitor.Replace(expr1.Body);
            var right = visitor.Replace(expr2.Body);
            var body = Expression.Or(left, right);
            return Expression.Lambda<Func<T, bool>>(body, newParameter);
        }
        public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expr)
        {
            if (expr == null)
                return null;

            var candidateExpr = expr.Parameters[0];
            var body = Expression.Not(expr.Body);

            return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值