说起Lambda表达式,大家基本都很熟悉了,而表达式树(Expression Trees),则属于80%的工作中往往都用不到的那种技术,所以即便不是什么新技术,很多人对其理解都并不透彻。此文意图从表达式树基本技术点结合实际应用,逐步来看表达式树究竟是怎么一回事,希望能帮助读者彻底学会表达式树^_^

一、初见表达式树Expression<TDelegate>

不妨回想一下,你第一次使用表达式树是在哪里呢,比如Expression<TDelegate>?

比如在linq查询中,常常用到Where方法过滤,OrderBy排序等,调用的是IQueryable<TSource>的静态扩展类Queryable的静态扩展方法。

 // C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.6.1\System.Core.dll
namespace System.Linq
{
//
// 摘要:提供一组用于查询实现 System.Linq.IQueryable`1 的数据结构的 static方法。
//
public static class Queryable
{
//...
public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate);
public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector);
//...
}
}

可以看到里面的参数类型Expression<Func<TSource, bool>> predicate和Expression<Func<TSource, TKey>> keySelector,咦?这不就是表达式树(Expression<TDelegate>)吗?!是的,但不止如此。

Expression<TDelegate>是表达式树的一种,并不等同于表达式树。

二、从Expression<TDelegate>到LambdaExpression

看下面的代码:

1  Func<Person, bool> func1 = x => x.Age > ;
2  Func<Person, string> func2 = x => x.Name;
3  Expression<Func<Person, bool>> expression1 = x => x.Age > ;
4  Expression<Func<Person, string>> expression2 = x => x.Name;
5  //错误CS0834,无法将具有语句体的lambda表达式转换为表达式树
6  Func<Person, int, int> func3 = (x, y) => { return x.Age + y; };
7  Action<Person, int> func4 = (x, y) => { };
8  Expression<Func<Person, int, int>> expression3 = (x, y) => { return x.Age + y; };
9  Expression<Action<Person, int>> expression4 = (x, y) => { };

expression1和expression2可以编译通过,expression3和expression4则编译错误,提示"无法将具有语句体的Lambda表达式转换为表达式树 "。

这是因为,我们可以将Lambda表达式赋值给泛型表达式类型变量(Expression<TDelegate>),编译器会将我们的Lambda表达式编译为表达式树,但是仅限于expression lambdas(表达式Lambdas,也叫做single-line lambdas,即不具有语句体的Lambda表达式),相对的statement lambdas(语句Lambdas,也叫做multi-line lambdas),编译器则会报错。

看看反编译后的func1,func2,expression1,expression2。

    ParameterExpression expression3;
Func<Person, bool> func = x => x.Age > 0x12;
Func<Person, string> func2 = x => x.Name;
ParameterExpression[] parameters = new ParameterExpression[] { expression3 };
Expression<Func<Person, bool>> expression = Expression.Lambda<Func<Person, bool>>(Expression.GreaterThan(Expression.Property(expression3 = Expression.Parameter(typeof(Person), "x"), (MethodInfo) methodof(Person.get_Age)), Expression.Constant(0x12, typeof(int))), parameters);
ParameterExpression[] expressionArray2 = new ParameterExpression[] { expression3 };
Expression<Func<Person, string>> expression2 = Expression.Lambda<Func<Person, string>>(Expression.Property(expression3 = Expression.Parameter(typeof(Person), "x"), (MethodInfo) methodof(Person.get_Name)), expressionArray2);

expression1和expression2变成了一堆复杂的语句,这才是它们作为表达式树原有的面目^_^

而Expression<TDelegate>来自何方?它继承自LambdaExpression。

来看看Expression<TDelegate>反编译后的构造函数

 [__DynamicallyInvokable]
public sealed class Expression<TDelegate> : LambdaExpression
{
// 构造函数
internal Expression(Expression body, string name, bool tailCall, ReadOnlyCollection<ParameterExpression> parameters) :
6   base(typeof(TDelegate), name, body, tailCall, parameters)
{
}
//...
}

构造函数什么也没有做,只是将参数传递给父类构造函数,所以可以认为Expression<TDelegate>是对LambdaExpression的一层封装,而LambdaExpression又是继承自Expression,是表达式树的一种,而且是最特别的一种,为何特别,且看下文^_^

三、何为表达式树

首先来看下面的类图,可以更直观看到Expression<TDelegate>、LambdaExpression、Expression三者的关系。

黄线表示,使用表达式Lambdas赋值给Expression<TDelegate>类型变量,TDelegate会赋值给父类LambdaExpression的Type属性(联系下前面讲的构造函数的事情 ^_^ )

重点看绿线,LambdaExpression继承自Expression,而LambdaExpression的Body属性,又是Expression类型。

哇哦,眼尖的你注意到LambdaExpression还有个Compile()方法,它会编译Body,生成表示 lambda 表达式的可执行委托,然后你就可以调用委托了,所以真正的表达式树所谓的树,就是藏在Body里啦

So,其实Expression的子类型多达几十种,例如ParameterExpression,BinaryExpression,MethodCallExpression等等,但是只有LambdaExpression(Lambda表达式树)可以执行!

如果你还有疑问,那么任意一棵其它类型的表达式树,怎样执行呢?答案自然是创建一个新的LambdaExpression了,将表达式树作为LambdaExpression的Body,然后你懂的^_^

具体可以调用这个方法 public static Expression<TDelegate> Lambda<TDelegate>(Expression body, params ParameterExpression[] parameters);

此时,我们再来解答最初的疑问,究竟何为表达式树(Expression Trees)?

表达式树即一份树形结构存储的代码,树的每个结点又都是一个表达式树,你可以编译然后运行这份树形代码。

那么可以用它做什么呢?

  1. 表示Lambda表达式,这个显而易见了;
  2. 修改可执行代码,创建动态查询;
  3. 定制自己的IQueryable,通过翻译表达式树数据结构为特定的查询语言,实现对特定数据源的查询,即定制orm。与我们对各种数据库的Linq查询同理;

也就是说小到根据字段字符串参数的过滤排序等,大到定制自己的orm,你都离不开表达式树。

下篇文章中,我们将就表达式树的具体用途,来做实例展示。

四、手动创建表达式树

可以通过哪些途径来创建表达式树呢?通过上文我们知道可以使用表达式Lambda由编译器来创建Lambda表达式树,例如Expression<Func<int, bool>> lambda = num => num < 5;

而另外一个更通用的方式,就是引入System.Linq.Expressions命名空间,手动构造每一个表达式树结点,来创建表达式树。

来看几个简单的例子,一看就懂。

 class Program
{
static void Main(string[] args)
{
var ints = new int[] { , , , , , , , , , };
Func1(ints);
Func2(ints);
Func3(ints);
Console.Read();
}
/// <summary>
/// 目标表达式树 x => x > 5 && x <= 7
/// </summary>
public static void Func1(int[] ints)
{
//创建参数x
var parameter = Expression.Parameter(typeof(int), "x");
//创建表达式x>5
var con1 = Expression.Constant();
var bin1 = Expression.GreaterThan(parameter, con1);
//创建表达式x<=7
var con2 = Expression.Constant();
var bin2 = Expression.LessThanOrEqual(parameter, con2);
//组合两个表达式
var body = Expression.And(bin1, bin2);
//获取Lambda表达式
var lambda = Expression.Lambda<Func<int, bool>>(body, parameter);
var ints2 = ints.Where(lambda.Compile());
//执行结果
Console.WriteLine("\r\n Func1构造的表达式树Body:\r\n {0}", body);
Console.WriteLine("\r\n Func1构造的表达式树:\r\n {0}", lambda);
Console.WriteLine("\r\n Func1的结果:\r\n {0}", string.Join(",", ints2));
}
/// <summary>
/// 目标表达式树 x => x % 2 == 0 ? x : 0
/// </summary>
public static void Func2(int[] ints)
{
//创建参数 x
var parameter = Expression.Parameter(typeof(int), "x");
//创建表达式 x % 2
var con1 = Expression.Constant();
var bin1 = Expression.Modulo(parameter, con1);
//创建表达式 (x % 2) == 0
var con2 = Expression.Constant();
var bin2 = Expression.Equal(bin1, con2);
//创建表达式 x % 2 == 0 ? x : 0
var body = Expression.Condition(bin2, parameter, Expression.Constant());
//获取Lambda表达式
var lambda = Expression.Lambda<Func<int, int>>(body, parameter);
var ints2 = ints.Select(lambda.Compile());
//执行结果
Console.WriteLine("\r\n Func2构造的表达式树Body:\r\n {0}", body);
Console.WriteLine("\r\n Func2构造的表达式树:\r\n {0}", lambda);
Console.WriteLine("\r\n Func2的结果:\r\n {0}", string.Join(",", ints2));
}
/// <summary>
/// 目标表达式树 x => Console.WriteLine(x)
/// </summary>
/// <param name="ints"></param>
public static void Func3(int[] ints)
{
//创建参数i
var parameter = Expression.Parameter(typeof(int), "x");
//获取Console.WriteLine MethodInfo
MethodInfo method = typeof(Console).GetMethod("WriteLine", new Type[] { typeof(int) });
//创建表达式
var body = Expression.Call(method, parameter);
var lambda = Expression.Lambda<Action<int>>(body, parameter);
Console.WriteLine("\r\n Func3构造的表达式树Body:\r\n {0}", body);
Console.WriteLine("\r\n Func3构造的表达式树:\r\n {0}", lambda);
Array.ForEach(ints, lambda.Compile());
}
}

输出结果:

怎么样,动手来写几个表达式树吧?!代码实战会让你更加轻松的入门表达式树技术。

这里贴出Func1中的lambda变量监视图,看看这棵树吧^_^

通过上面几个小例子,相信你对表达式树的手动创建已经基本了解,同时可以看到手动创建远没有使用表达式Lambda来的方便,但是却可以突破single-line lambdas限制实现更多强大的功能。

那么Expression有多少种呢?看下图。这些都是继承自Expression,至于每种的用途,可以在VS中自行浏览,使用时根据我们的实际用途来选用不同的Expression来构建表达式树即可。

这些表达式目录树节点的类型共有85种。

     //
// 摘要:
// 描述表达式目录树的节点的节点类型。
public enum ExpressionType
{
//
// 摘要:
// 加法运算,如 a + b,针对数值操作数,不进行溢出检查。
Add = ,
//
// 摘要:
// 加法运算,如 (a + b),针对数值操作数,进行溢出检查。
AddChecked = ,
//
// 摘要:
// 按位或逻辑 AND 运算,如 C# 中的 (a & b) 和 Visual Basic 中的 (a And b)。
And = ,
//
// 摘要:
// 条件 AND 运算,它仅在第一个操作数的计算结果为 true 时才计算第二个操作数。它与 C# 中的 (a && b) 和 Visual Basic 中的
// (a AndAlso b) 对应。
AndAlso = ,
//
// 摘要:
// 获取一维数组长度的运算,如 array.Length。
ArrayLength = ,
//
// 摘要:
// 一维数组中的索引运算,如 C# 中的 array[index] 或 Visual Basic 中的 array(index)。
ArrayIndex = ,
//
// 摘要:
// 方法调用,如在 obj.sampleMethod() 表达式中。
Call = ,
//
// 摘要:
// 表示 null 合并运算的节点,如 C# 中的 (a ?? b) 或 Visual Basic 中的 If(a, b)。
Coalesce = ,
//
// 摘要:
// 条件运算,如 C# 中的 a > b ? a : b 或 Visual Basic 中的 If(a > b, a, b)。
Conditional = ,
//
// 摘要:
// 一个常量值。
Constant = ,
//
// 摘要:
// 强制转换或转换运算,如 C#中的 (SampleType)obj 或 Visual Basic 中的 CType(obj, SampleType)。对于数值转换,如果转换后的值对于目标类型来说太大,这不会引发异常。
Convert = ,
//
// 摘要:
// 强制转换或转换运算,如 C#中的 (SampleType)obj 或 Visual Basic 中的 CType(obj, SampleType)。对于数值转换,如果转换后的值与目标类型大小不符,则引发异常。
ConvertChecked = ,
//
// 摘要:
// 除法运算,如 (a / b),针对数值操作数。
Divide = ,
//
// 摘要:
// 表示相等比较的节点,如 C# 中的 (a == b) 或 Visual Basic 中的 (a = b)。
Equal = ,
//
// 摘要:
// 按位或逻辑 XOR 运算,如 C# 中的 (a ^ b) 或 Visual Basic 中的 (a Xor b)。
ExclusiveOr = ,
//
// 摘要:
// “大于”比较,如 (a > b)。
GreaterThan = ,
//
// 摘要:
// “大于或等于”比较,如 (a >= b)。
GreaterThanOrEqual = ,
//
// 摘要:
// 调用委托或 lambda 表达式的运算,如 sampleDelegate.Invoke()。
Invoke = ,
//
// 摘要:
// lambda 表达式,如 C# 中的 a => a + a 或 Visual Basic 中的 Function(a) a + a。
Lambda = ,
//
// 摘要:
// 按位左移运算,如 (a << b)。
LeftShift = ,
//
// 摘要:
// “小于”比较,如 (a < b)。
LessThan = ,
//
// 摘要:
// “小于或等于”比较,如 (a <= b)。
LessThanOrEqual = ,
//
// 摘要:
// 创建新的 System.Collections.IEnumerable 对象并从元素列表中初始化该对象的运算,如 C# 中的 new List<SampleType>(){
// a, b, c } 或 Visual Basic 中的 Dim sampleList = { a, b, c }。
ListInit = ,
//
// 摘要:
// 从字段或属性进行读取的运算,如 obj.SampleProperty。
MemberAccess = ,
//
// 摘要:
// 创建新的对象并初始化其一个或多个成员的运算,如 C# 中的 new Point { X = 1, Y = 2 } 或 Visual Basic 中的 New
// Point With {.X = 1, .Y = 2}。
MemberInit = ,
//
// 摘要:
// 算术余数运算,如 C# 中的 (a % b) 或 Visual Basic 中的 (a Mod b)。
Modulo = ,
//
// 摘要:
// 乘法运算,如 (a * b),针对数值操作数,不进行溢出检查。
Multiply = ,
//
// 摘要:
// 乘法运算,如 (a * b),针对数值操作数,进行溢出检查。
MultiplyChecked = ,
//
// 摘要:
// 算术求反运算,如 (-a)。不应就地修改 a 对象。
Negate = ,
//
// 摘要:
// 一元加法运算,如 (+a)。预定义的一元加法运算的结果是操作数的值,但用户定义的实现可以产生特殊结果。
UnaryPlus = ,
//
// 摘要:
// 算术求反运算,如 (-a),进行溢出检查。不应就地修改 a 对象。
NegateChecked = ,
//
// 摘要:
// 调用构造函数创建新对象的运算,如 new SampleType()。
New = ,
//
// 摘要:
// 创建新的一维数组并从元素列表中初始化该数组的运算,如 C# 中的 new SampleType[]{a, b, c} 或 Visual Basic 中的
// New SampleType(){a, b, c}。
NewArrayInit = ,
//
// 摘要:
// 创建新数组(其中每个维度的界限均已指定)的运算,如 C# 中的 new SampleType[dim1, dim2] 或 Visual Basic 中的
// New SampleType(dim1, dim2)。
NewArrayBounds = ,
//
// 摘要:
// 按位求补运算或逻辑求反运算。在 C# 中,它与整型的 (~a) 和布尔值的 (!a) 等效。在 Visual Basic 中,它与 (Not a) 等效。不应就地修改
// a 对象。
Not = ,
//
// 摘要:
// 不相等比较,如 C# 中的 (a != b) 或 Visual Basic 中的 (a <> b)。
NotEqual = ,
//
// 摘要:
// 按位或逻辑 OR 运算,如 C# 中的 (a | b) 或 Visual Basic 中的 (a Or b)。
Or = ,
//
// 摘要:
// 短路条件 OR 运算,如 C# 中的 (a || b) 或 Visual Basic 中的 (a OrElse b)。
OrElse = ,
//
// 摘要:
// 对在表达式上下文中定义的参数或变量的引用。有关详细信息,请参阅System.Linq.Expressions.ParameterExpression。
Parameter = ,
//
// 摘要:
// 对某个数字进行幂运算的数学运算,如 Visual Basic 中的 (a ^ b)。
Power = ,
//
// 摘要:
// 具有类型为 System.Linq.Expressions.Expression 的常量值的表达式。System.Linq.Expressions.ExpressionType.Quote
// 节点可包含对参数的引用,这些参数在该节点表示的表达式的上下文中定义。
Quote = ,
//
// 摘要:
// 按位右移运算,如 (a >> b)。
RightShift = ,
//
// 摘要:
// 减法运算,如 (a - b),针对数值操作数,不进行溢出检查。
Subtract = ,
//
// 摘要:
// 算术减法运算,如 (a - b),针对数值操作数,进行溢出检查。
SubtractChecked = ,
//
// 摘要:
// 显式引用或装箱转换,其中如果转换失败则提供 null,如 C# 中的 (obj as SampleType) 或 Visual Basic 中的 TryCast(obj,
// SampleType)。
TypeAs = ,
//
// 摘要:
// 类型测试,如 C# 中的 obj is SampleType 或 Visual Basic 中的 TypeOf obj is SampleType。
TypeIs = ,
//
// 摘要:
// 赋值运算,如 (a = b)。
Assign = ,
//
// 摘要:
// 表达式块。
Block = ,
//
// 摘要:
// 调试信息。
DebugInfo = ,
//
// 摘要:
// 一元递减运算,如 C# 和 Visual Basic 中的 (a - 1)。不应就地修改 a 对象。
Decrement = ,
//
// 摘要:
// 动态操作。
Dynamic = ,
//
// 摘要:
// 默认值。
Default = ,
//
// 摘要:
// 扩展表达式。
Extension = ,
//
// 摘要:
// “跳转”表达式,如 C# 中的 goto Label 或 Visual Basic 中的 GoTo Label。
Goto = ,
//
// 摘要:
// 一元递增运算,如 C# 和 Visual Basic 中的 (a + 1)。不应就地修改 a 对象。
Increment = ,
//
// 摘要:
// 索引运算或访问使用参数的属性的运算。
Index = ,
//
// 摘要:
// 标签。
Label = ,
//
// 摘要:
// 运行时变量的列表。有关详细信息,请参阅System.Linq.Expressions.RuntimeVariablesExpression。
RuntimeVariables = ,
//
// 摘要:
// 循环,如 for 或 while。
Loop = ,
//
// 摘要:
// 多分支选择运算,如 C# 中的 switch 或 Visual Basic 中的 Select Case。
Switch = ,
//
// 摘要:
// 引发异常的运算,如 throw new Exception()。
Throw = ,
//
// 摘要:
// try-catch 表达式。
Try = ,
//
// 摘要:
// 取消装箱值类型运算,如 MSIL 中的 unbox 和 unbox.any 指令。
Unbox = ,
//
// 摘要:
// 加法复合赋值运算,如 (a += b),针对数值操作数,不进行溢出检查。
AddAssign = ,
//
// 摘要:
// 按位或逻辑 AND 复合赋值运算,如 C# 中的 (a &= b)。
AndAssign = ,
//
// 摘要:
// 除法复合赋值运算,如 (a /= b),针对数值操作数。
DivideAssign = ,
//
// 摘要:
// 按位或逻辑 XOR 复合赋值运算,如 C# 中的 (a ^= b)。
ExclusiveOrAssign = ,
//
// 摘要:
// 按位左移复合赋值运算,如 (a <<= b)。
LeftShiftAssign = ,
//
// 摘要:
// 算术余数复合赋值运算,如 C# 中的 (a %= b)。
ModuloAssign = ,
//
// 摘要:
// 乘法复合赋值运算,如 (a *= b),针对数值操作数,不进行溢出检查。
MultiplyAssign = ,
//
// 摘要:
// 按位或逻辑 OR 复合赋值运算,如 C# 中的 (a |= b)。
OrAssign = ,
//
// 摘要:
// 对某个数字进行幂运算的复合赋值运算,如 Visual Basic 中的 (a ^= b)。
PowerAssign = ,
//
// 摘要:
// 按位右移复合赋值运算,如 (a >>= b)。
RightShiftAssign = ,
//
// 摘要:
// 减法复合赋值运算,如 (a -= b),针对数值操作数,不进行溢出检查。
SubtractAssign = ,
//
// 摘要:
// 加法复合赋值运算,如 (a += b),针对数值操作数,进行溢出检查。
AddAssignChecked = ,
//
// 摘要:
// 乘法复合赋值运算,如 (a *= b),针对数值操作数,进行溢出检查。
MultiplyAssignChecked = ,
//
// 摘要:
// 减法复合赋值运算,如 (a -= b),针对数值操作数,进行溢出检查。
SubtractAssignChecked = ,
//
// 摘要:
// 一元前缀递增,如 (++a)。应就地修改 a 对象。
PreIncrementAssign = ,
//
// 摘要:
// 一元前缀递减,如 (--a)。应就地修改 a 对象。
PreDecrementAssign = ,
//
// 摘要:
// 一元后缀递增,如 (a++)。应就地修改 a 对象。
PostIncrementAssign = ,
//
// 摘要:
// 一元后缀递减,如 (a--)。应就地修改 a 对象。
PostDecrementAssign = ,
//
// 摘要:
// 确切类型测试。
TypeEqual = ,
//
// 摘要:
// 二进制反码运算,如 C# 中的 (~a)。
OnesComplement = ,
//
// 摘要:
// true 条件值。
IsTrue = ,
//
// 摘要:
// false 条件值。
IsFalse =
}

--参考阅读--

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/

不可不知的表达式树(1)Expression初探的更多相关文章

  1. 不可不知的表达式树(3)定制IQueryProvider

    前面我们说到利用表达式树技术实现LINQ-to-SQL,实际上可以针对任何数据源,实现LINQ-to-Everything.这里还涉及到两个重要的接口即IQueryable和IQueryProvide ...

  2. 深入学习C#匿名函数、委托、Lambda表达式、表达式树类型——Expression tree types

    匿名函数 匿名函数(Anonymous Function)是表示“内联”方法定义的表达式.匿名函数本身及其内部没有值或者类型,但是可以转换为兼容的委托或者表达式树类型(了解详情).匿名函数转换的计算取 ...

  3. 表达式树(Expression Trees)

    [翻译]表达式树(Expression Trees) 原文地址:https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/con ...

  4. C# 表达式树(Expression)

    c#中有Expression,即表达式. 通过Expression可以动态构造代码,并编译执行.  比如: 1.  创建参数表达式 :ParameterExpression numParam = Ex ...

  5. 表达式树(Expression Tree)

    你每创建一个表示表达式的实例时,都可以将该类型实例看成是一棵表达式树.每种表示表达式的类型都有一个具体的类型,如Expression的Variable()方法创建的是ParameterExpressi ...

  6. [C#] C# 知识回顾 - 表达式树 Expression Trees

    C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...

  7. 表达式树(Expression Tree)

    饮水思源 本文并非原创而是下面网址的一个学习笔记 https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/e ...

  8. C# 知识回顾 - 表达式树 Expression Trees

    C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...

  9. 【C#表达式树 开篇】 Expression Tree - 动态语言

    .NET 3.5中新增的表达式树(Expression Tree)特性,第一次在.NET平台中引入了"逻辑即数据"的概念.也就是说,我们可以在代码里使用高级语言的形式编写一段逻辑, ...

随机推荐

  1. 51nod 1630(定积分 + 期望)

    51nod1630 每个人进入竞技场后,会等概率随机匹配一个人,匹配到的人与当前胜利和失败场数无关. 胜利达到x场,或失败达到y场后,退出竞技场,根据退出时的胜利场数获得奖励,不能中途放弃. 水平高的 ...

  2. Linux uniq 命令

    Linux uniq 命令  Linux 命令大全 Linux uniq 命令用于检查及删除文本文件中重复出现的行列,一般与 sort 命令结合使用. uniq 可检查文本文件中重复出现的行列. 语法 ...

  3. Linux samba服务器的搭建

    目录 1. 安装samba 2. 配置smb.conf 3. 创建samba登陆用户 4. 创建samba共享文件夹 5. 重启samba服务 6. 访问samba共享文件夹 7. 参考资料 1. 安 ...

  4. Aras SP9里打开自己写的网页。

    首先把自己写的网页挂在IIS里或者网站挂到IIS里面. 然后再Aras里新增method //网页参数 var dialogArguments = new Array(); //窗体参数 var op ...

  5. springboot springmvc拦截器 拦截POST、PUT、DELETE请求参数和响应数据,并记录操作日志

    1.操作日志实体类 @Document(collection = "operation_log") @Getter @Setter @ToString public class O ...

  6. Thinkphp生成的验证码不显示——解决方法

    在调用验证码之前加上 ob_clean(); 不显示验证码的代码: public function verify(){ $verify = new \Think\Verify(); $verify-& ...

  7. JDK8源码阅读之Collection及相关方法

    最近面试总会被问到JDK8中的一些新特性,所以闲下来抽时间看了一下8的源码,目前主要看的是数据结构部分,特此记录一下. 新增函数式接口,实现该接口的可以直接用lambda表达式. default和st ...

  8. 贪吃蛇游戏——C语言双向链表实现

    采用了双向链表结点来模拟蛇身结点: 通过C语言光标控制函数来打印地图.蛇身和食物: /************************** *************************** 贪吃 ...

  9. easyui datagrid使用按钮

    $('#datagrid').datagrid({ border:false, fitColumns:true, singleSelect: true, url:url, columns:[[ {fi ...

  10. Spring框架-AOP详细学习[转载]

    参考博客:https://blog.csdn.net/qq_22583741/article/details/79589910#4-%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85% ...