CSharp-CodeDom创建XML文档
可使用 CodeDOM 创建生成 XML 文档的代码。 该进程包括创建包含 XML 文档注释的 CodeDOM 图、生成代码和通过创建 XML 文档输出的编译器选项编译生成的代码。
1 | namespace ConsoleApp1 |
可使用 CodeDOM 创建生成 XML 文档的代码。 该进程包括创建包含 XML 文档注释的 CodeDOM 图、生成代码和通过创建 XML 文档输出的编译器选项编译生成的代码。
1 | namespace ConsoleApp1 |
System.CodeDom.Compiler
命名空间提供了从 CodeDOM 对象图生成源代码和用受支持的编译器管理编译的接口。 代码提供程序可根据 CodeDOM 图,用某种编程语言生成源代码。 从 CodeDomProvider
派生的类通常可以提供一些方法,用于生成和编译提供程序支持的语言代码。
参考:
1 | using System; |
CodeDOM:代码文档对象模型。
参考:动态源代码生成和编译
.NET Framework 包括一个名为代码文档对象模型 (CodeDOM) 的机制。通过该机制,发出源代码的程序开发者可基于一种表示要呈现的代码的单一模型,在运行时以多种编程语言生成源代码。
为表示源代码,CodeDOM 元素相互链接,构成一个名为 CodeDOM 图的数据结构。该数据结构对某些源代码的结构建模。
System.CodeDom
命名空间定义某些类型,这些类型可以表示源代码的逻辑结构,且不依赖特定编程语言。 System.CodeDom.Compiler
命名空间定义某些类型,用于从 CodeDOM 图中生成源代码,并管理使用支持语言的源代码的编译工作。 编译器供应商或开发人员可以扩展支持语言。
.NET Framework 包括适用于 CSharpCodeProvider
、JScriptCodeProvider
和 VBCodeProvider
的代码生成器和代码编译器。
CodeDOM 提供表示多种常见源代码元素的类型。 可以设计一个程序,它使用 CodeDOM 元素生成源代码模型来组合对象图。 对于支持的编程语言,可使用 CodeDOM 代码生成器将此对象图呈现为源代码。 还可使用 CodeDOM 将源代码编译为二进制程序集。
CodeDOM 的常见用途包括:
参考:使用 CodeDom
System.CodeDom
命名空间提供用于表示源代码逻辑结构的类,独立于语言语法。
CodeDOM 图的结构类似于一个容器树。 每个可编译 CodeDOM 图的顶端容器或根部容器是 CodeCompileUnit
。 源代码模型中的每个元素都必须通过图中 CodeObject
的属性链接至该图。
CodeDOM 定义名为 CodeCompileUnit
的对象,可引用 CodeDOM 对象图,该对象图塑造要编译的源代码的模型。 CodeCompileUnit
具有属性可用于存储对特性、命名空间和程序集的引用。
派生自 CodeDomProvider
类的 CodeDom 提供程序包含处理 CodeCompileUnit 所引用的对象图的方法。
详细请参考:如何:使用 CodeDOM 创建类
1 | namespace ConsoleApp1 |
生成类的代码:
1 | //------------------------------------------------------------------------------ |
某方法只要属于泛型类型,且使用该类型的类型参数,就不是泛型方法。 只有当方法有属于自己的类型参数列表时才是泛型方法。
参考:用反射发出定义泛型方法
如果发出对泛型类型方法的调用,并且这些类型的类型参数是泛型方法的类型参数,则必须使用 TypeBuilder 类的 staticGetConstructor(Type, ConstructorInfo)、GetMethod(Type, MethodInfo) 和 GetField(Type, FieldInfo) 方法重载来获取方法的构造窗体。
1 | namespace ConsoleApp1 |
某方法只要属于泛型类型,且使用该类型的类型参数,就不是泛型方法。 只有当方法有属于自己的类型参数列表时才是泛型方法。
详细信息,请参考https://docs.microsoft.com/zh-cn/dotnet/api/system.reflection.emit.dynamicmethod?view=netframework-4.7.2
参考:发出动态方法和程序集
System.Reflection.Emit
命名空间提供的功能被称为反射发出。 System.Reflection.Emit
命名空间中的一组托管类型,它们允许编译器或工具在运行时发出元数据和 Microsoft 中间语言 (MSIL),并在磁盘上生成可移植可执行 (PE) 文件(可选)。
反射发出具有以下功能:
DynamicMethod
类)并通过委托执行这些方法。参考:
使用反射加载和运行程序集时,不能使用 C# += 运算符将事件挂钩。
参考:访问自定义特性
加载到仅反射上下文中的代码无法执行。在仅反射上下文中加载和检查自定义特性,使用 CustomAttributeData
类。通过使用静态 CustomAttributeData.GetCustomAttributes
方法的相应重载来获取此类的实例。
用于查询执行上下文中的特性的主要反射方法是 MemberInfo.GetCustomAttributes
和 Attribute.GetCustomAttributes
。
自定义特性的可访问性根据附加该特性的程序集来进行检查。
Assembly.GetCustomAttributes(Boolean)
等方法检查类型参数的可见性和可访问性。 只有包含用户定义类型的程序集中的代码才能使用 GetCustomAttributes
检索该类型的自定义特性。
自定义特性反射模型可能会在定义类型的程序集外泄漏用户定义类型的实例。 为了防止客户端发现关于用户定义的自定义特性类型的信息,请将该类型的成员定义为非公共成员。
1 | using System; |
参考:指定完全限定的类型名称
本文包括动态加载和使用类型,将程序集加载到仅反射上下文中;
参考:自定义绑定
反射可在代码中显式用于完成后期绑定。在通过反射实现的后期绑定中,必须由自定义绑定来控制该绑定。 Binder
类提供对成员选择和调用的自定义控制。
使用自定义绑定可以在运行时加载程序集、获取该程序集中有关类型的信息、指定所需类型,并在之后调用该类型上的方法或访问该类型上的字段或属性。 如果在编译时不知道对象的类型,则此方法非常有用,例如当对象类型取决于用户输入时。
实例:
1 | namespace ClassLibrary1 |
使用 Type.InvokeMember
调用类型的成员。InvokeMember
可以创建指定类型的新实例,而各种类的 CreateInstance
方法(例如 Activator.CreateInstance
和 Assembly.CreateInstance
)是 InvokeMember
的专用形式。 Binder
类可在这些方法中用于重载解析和参数强制转换。
实例:
1 | public class CustomBinderDriver |
通过仅反射加载上下文,可检查为其他平台或 .NET Framework 的其他版本编译的程序集。 只能检查,不能执行加载到此上下文中的代码。 这意味着无法创建对象,因为无法执行构造函数。 因为该代码无法执行,所以不会自动加载依赖项。 如果该程序集具有依赖项,ReflectionOnlyLoad
方法不会加载这些依赖项。如果需要对依赖项进行检查,必须自行加载。使用 CustomAttributeData.GetCustomAttributes
方法的适当重载获取表示应用于程序集、成员、模块或参数的特性的 CustomAttributeData
对象。
1 | using System; |
本章包括查看对象类型信息,反射类型和泛型反射,反射检查和实例化泛型类型。
在程序中,当我们需要动态的去加载程序集的时候(将对程序集的引用由编译时推移到运行时),反射是一种很好的选择。可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问其字段和属性。反射为.NET类型提供了高度的动态能力,包括:元数据的动态查询、绑定与执行、动态代码生成。常用的反射类型包含在System.Reflection
和 System.Reflection.Emit
,反射包括程序集反射、类型反射、接口反射、类型成员反射。
反射在以下情况下很有用:
System.Reflection.Emit
中的类。运行时类型信息 (RTTI,Run-Time Type Information) 是一种允许在程序执行过程中确定对象类型的机制。例如使用它能够确切地知道基类引用指向了什么类型对象。运行时类型标识,能预先测试某个强制类型转换操作,能否成功,从而避免无效的强制类型转换异常。
在c#中有三个支持RTTI的关键字:is
、 as
、typeof
。
参考:is(C# 参考)
能够判断对象类型是否为特定类型,如果两种类型是相同类型,或者两者之间存在引用,装箱拆箱转换,则表明两种类型是兼容的。或(从 C# 7.0 开始)针对某个模式测试表达式。
语法:
1 | expr is type |
expr 是计算结果为某个类型的实例的表达式,而 type 是 expr 结果要转换到的类型的名称。
expr 可以是返回值的任何表达式,匿名方法和 Lambda 表达式除外。
如果满足以下条件,则 is
语句为 true:
从 C# 7.0 开始,is
和 switch 语句支持模式匹配。
** 类型模式**
用于测试表达式是否可转换为指定类型,如果可以,则将其转换为该类型的一个变量。
语法:
1 | expr is type varname |
如果 expr 为 true,且 is
与 if
语句一起使用,则会分配 varname,并且其仅在 if
语句中具有局部范围。
1 | if (o is Employee e) |
** 常量模式**
使用常量模式执行模式匹配时,is
会测试表达式结果是否等于指定常量。
1 | expr is constant |
其中 expr 是要计算的表达式,constant 是要测试的值。 constant 可以是以下任何常数表达式:
常数表达式的计算方式如下:
expr == constant
)。Object.Equals(expr, constant)
方法的调用来确定表达式的值。is
语句支持 null 关键字。
1 | expr is null |
** var 模式**
具有 var 模式的模式匹配始终成功。 语法为
1 | expr is var varname |
1 | foreach (var item in items) { |
请注意,如果 expr 为 null,则 is 表达式仍为 true 并向 varname 分配 null。
参考:as(C# 参考)
在运行期间执行类型转换,并且能够使得类型转换失败不抛异常,而返回一个null值。
1 | expression as type |
1 | Base b = d as Base; |
获取类型的System.Type 对象。
1 | System.Type type = typeof(int); |
获取表达式的运行时类型:
1 | int i = 0; |
示例显示如何确定方法的返回类型是否为泛型 IEnumerable<T>
。 如果返回类型不是 IEnumerable<T>
泛型类型,则 Type.GetInterface
将返回 null
。
1 | MethodInfo method = typeof(string).GetMethod("Copy"); |
参考:查看类型信息
模块是程序集中代码的逻辑集合。您可以在程序集内部拥有多个模块,并且每个模块都可以使用不同的.NET语言编写(VS不支持创建多模块程序集)。
程序集包含模块。模块包含类。类包含函数。
System.Type
类是反射的中心。 当反射提出请求时,公共语言运行时为已加载的类型创建 Type。 可使用 Type 对象的方法、字段、属性和嵌套类来查找该类型的任何信息。
Type
类派生于 System.Reflection.MemberInfo
抽象类。
获取程序集的 Assembly
对象和模块:
1 | Assembly a = typeof(object).Module.Assembly; |
从已加载的程序集获取 Type 对象:
1 | // Loads an assembly using its file name. |
使用 Type.Module
属性获取一个封装含该类型的模块的对象。 使用 Module.Assembly
属性查找一个封装含该模块的程序集的对象。 可以获取直接使用 Type.Assembly
属性封装类型的程序集。
至少要有Instance(或Static)与Public(或NonPublic)标记。
1 | Type.GetMembers() // 获取所有公共成员。不包括 private 和 protected 访问权限 |
1 | Type.GetFields() // 获取字段 |
列出类的构造函数
1 | private static void Main(string[] args) |
从反射的角度来说,泛型类型和普通类型之间的区别在于泛型类型具有与之关联的一组类型形参(若是泛型类型定义)或类型实参(若是构造类型)。
检查未知类型(由 Type的实例表示),使用 IsGenericType
属性来确定未知类型是否为泛型。如果类型是泛型,它会返回 true 。
检查未知方法(由 MethodInfo 类的实例表示)时,请使用 IsGenericMethod
属性来确定此方法是否为泛型。
使用 IsGenericTypeDefinition
属性来确定 Type
对象是否表示泛型类型定义,并使用 IsGenericMethodDefinition
方法来确定 MethodInfo
是否表示泛型方法定义。
详细请参考:Type.IsGenericTypeDefinition Property
Type.GetGenericTypeDefinition Method
如果类型为开放式, Type.ContainsGenericParameters
属性将返回 true 。 对于方法, MethodBase.ContainsGenericParameters
方法执行相同的功能。
MakeGenericType
方法来创建封闭式泛型类型;MakeGenericMethod
方法来为封闭式泛型方法创建 MethodInfo
;
开放式泛型类型或方法(非泛型类型或方法定义),不能创建它的实例,也不能提供缺少的类型形参。
GetGenericTypeDefinition
方法来获取泛型类型定义;GetGenericMethodDefinition
方法来获取泛型方法定义。
例如,如果你有一个表示 Type
的 Dictionary<int, string>
对象且想创建类型 Dictionary<string, MyClass>
,则可以使用 GetGenericTypeDefinition
方法来获取表示 Type
的 Dictionary<TKey, TValue>
,然后使用 MakeGenericType
方法来生成表示 Type
的 Dictionary<int, MyClass>
。
Type.GetGenericArguments
方法来获取 Type
对象的数组(表示泛型类型的类型形参或类型实参); MethodInfo.GetGenericArguments
方法为泛型方法获取 MethodInfo
(表示泛型类型的类型形参或类型实参)。如果元素是类型形参,则 IsGenericParameter
属性为 true
。
获取泛型类型信息的方式与获取其他类型信息的方式相同:检查表示泛型类型的 Type 对象。 主要的差异在于,泛型类型具有表示其泛型类型参数的 Type 对象列表。
范例:
1 | private static void Main(string[] args) |
需要指定其泛型类型参数的实际类型,否则不能创建泛型类型的实例。使用 MakeGenericType
方法。
实例:
1 | using System; |
参考:
设计类和模块时,遵守 SOLID 原则可以让软件更加健壮和稳定。
简写 | 全称 | 翻译 |
---|---|---|
SRP | The Single Responsibility Principle | 单一责任原则 |
OCP | The Open Closed Principle | 开放封闭原则 |
LSP | The Liskov Substitution Principle | 里氏替换原则 |
ISP | The Interface Segregation Principle | 接口分离原则 |
DIP | The Dependency Inversion Principle | 依赖倒置原则 |
单一职责原则(SRP)表明一个类有且只有一个职责。
开放封闭原则(OCP)指出,一个类应该对扩展开放,对修改关闭。这意味一旦你创建了一个类并且应用程序的其他部分开始使用它,你不应该修改它。即,可扩展(extension),不可修改(modification)。
里氏替换原则指出,派生的子类应该是可替换基类的,也就是说任何基类可以出现的地方,子类一定可以出现。一个对象在其出现的任何地方,都可以用子类实例做替换,并且不会导致程序的错误。换句话说,当子类可以在任意地方替换基类且软件功能不受影响时,这种继承关系的建模才是合理的。当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。
接口隔离原则(ISP)表明类不应该被迫依赖他们不使用的方法,也就是说一个接口应该拥有尽可能少的行为。将接口拆分成更小和更具体的接口,有助于解耦,从而更容易重构、更改。
依赖倒置原则(DIP)表明高层模块不应该依赖低层模块,相反,他们应该依赖抽象类或者接口。这个设计原则的亮点在于任何被DI框架注入的类很容易用mock对象进行测试和维护,因为对象创建代码集中在框架中,客户端代码也不混乱。
参考:
IQueryable<T>
接口是 Linq to Provider
的入口。
在.NET中,IQueryable<T>
继承于 IEnumerable<T>
, IEnumerable
和 IQueryable
接口
1 | public interface IQueryable<out T> : IEnumerable<T>, IEnumerable, IQueryable |
IQueryable
的实例关联的表达式树我们所有定义在查询表达式中方法调用或者Lambda表达式都将由该Expression属性表示,最终会由Provider表示的提供程序翻译为它所对应的数据源的查询语言。
IQueryable
扩展方法传入的是 Expression
类型
IEnumrable
扩展方法传入的是委托。
1 | public interface IEnumerable<out T> : IEnumerable |
1 | public interface IQueryProvider |
Provider
负责执行表达式目录树并返回结果。如果是 LINQ to SQL
的 Provider
,则它会负责把表达式目录树翻译为T-SQL语句并并传递给数据库服务器,并返回最后的执行的结果;如果是一个 Web Service
的 Provider
,则它会负责翻译表达式目录树并调用 Web Service
,最终返回结果。
首先,实现一个 Query<T>
类,它实现 IQueryable<T>
接口,提供了一个 IQueryProvider
和 Expression
属性。
1 | public class Query<T> : IQueryable<T>, IQueryable, IEnumerable<T>, IEnumerable, IOrderedQueryable<T>, IOrderedQueryable |
然后,定义一个抽象类 QueryProvider
,它将会实现 IQueryProvider
接口,包括 CreateQuery
和 Execute
方法(都包含泛型方法)。
1 | public abstract class QueryProvider : IQueryProvider |
最后定义一个具体 Provider
,继承自抽象类 QueryProvider
;
1 | public class CnblogsQueryProvider : QueryProvider |
调用:
1 | static void Main(string[] args) |
源码:https://github.com/syxdevcode/LinqToCnBlogs
参考:
打造自己的LINQ Provider(中):IQueryable和IQueryProvider
可以使用 ExpressionVisitor 类遍历现有表达式树,以及复制它访问的每个节点。
参考:
表达式类型 | 表达式操作节点类型 | ExpressionVisitor类访问方法 |
---|---|---|
UnaryExpression | ExpressionType.ArrayLength ExpressionType.ArrayLength ExpressionType.Convert ExpressionType.ConvertChecked ExpressionType.Decrement ExpressionType.Increment ExpressionType.Negate ExpressionType.NegateChecked ExpressionType.Not ExpressionType.NotEqual ExpressionType.Quote ExpressionType.TypeAs | VisitUnary(UnaryExpression) |
BinaryExpression | ExpressionType.Add ExpressionType.AddAssignExpressionType.AddAssignCheckedExpressionType.AddCheckedExpressionType.AndExpressionType.AndAlsoExpressionType.AndAssignExpressionType.ArrayIndexExpressionType.AssignExpressionType.CoalesceExpressionType.DivideExpressionType.DivideAssignExpressionType.EqualExpressionType.GreaterThanExpressionType.GreaterThanOrEqualExpressionType.LessThanExpressionType.LessThanOrEqualExpressionType.ModuloExpressionType.ModuloAssignExpressionType.MultiplyExpressionType.MultiplyAssignExpressionType.NotEqualExpressionType.OrExpressionType.OrElseExpressionType.Subtract | VisitBinary(BinaryExpression) |
BlockExpression | ExpressionType.Block | VisitBlock(BlockExpression) |
ConditionalExpression | ExpressionType.Conditional | VisitConditional(ConditionalExpression) |
ConstantExpression | ExpressionType.Constant | VisitConstant(ConstantExpression) |
ParameterExpression | ExpressionType.Parameter | VisitParameter(ParameterExpression) |
MemberExpression | ExpressionType.MemberAccess | VisitMember(MemberExpression) |
MemberInitExpression | ExpressionType.MemberInit | VisitMemberInit(MemberInitExpression) |
MethodCallExpression | ExpressionType.Call | VisitMethodCall(MethodCallExpression) |
LambdaExpression | ExpressionType.Lambda | VisitLambda() |
NewExpression | ExpressionType.New | VisitNew(NewExpression) |
NewArrayExpression | ExpressionType.NewArrayBoundsExpressionType.NewArrayInit | VisitNewArray(NewArrayExpression) |
InvocationExpression | ExpressionType.Invoke | VisitInvocation(InvocationExpression) |
ListInitExpression | ExpressionType.ListInit | VisitListInit(ListInitExpression) |
TypeBinaryExpression | ExpressionType.TypeIs | VisitTypeBinary(TypeBinaryExpression) |
扩展IQueryable的Where方法,根据输入的查询条件来构造SQL语句。
1 | internal class Program |
1 | public class OperationsVisitor : ExpressionVisitor |
参考:
表达式树(Expression Tree)是.NET 3.5推出的。
表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。
表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性,同时保证编译器编写员能够发射表达式树而非 Microsoft 中间语言 (MSIL)。
System.Linq.Expressions
命名空间包含一些类、接口和枚举,它们使语言级别的代码表达式能够表示为表达式目录树形式的对象。
类 | 解释 |
---|---|
BinaryExpression | 表示具有二进制运算符的表达式。 |
UnaryExpression | 表示具有一元运算符的表达式。 |
BlockExpression | 表示包含一个表达式序列的块,表达式中可定义变量。 |
CatchBlock | 表示 try 块中的 catch 语句。 |
ConditionalExpression | 表示包含条件运算符的表达式。 |
ConstantExpression | 表示具有常量值的表达式。 |
GotoExpression | 表示无条件跳转。 这包括返回语句,break 和 continue 语句以及其他跳转。 |
IndexExpression | 表示对一个属性或数组进行索引。 |
LabelTarget | 用于表示 GotoExpression 的目标。 |
LambdaExpression | 介绍 lambda 表达式。 它捕获一个类似于 .NET 方法主体的代码块。 |
ListInitExpression | 表示具有集合初始值设定项的构造函数调用。 |
LoopExpression | 表示无限循环。 可通过“中断”退出该循环。 |
MemberAssignment | 表示对象的字段或属性的赋值操作。 |
MemberBinding | 提供表示绑定的类派生自的基类,这些绑定用于对新创建对象的成员进行初始化。 |
MemberExpression | 表示访问字段或属性。 |
MemberInitExpression | 表示调用构造函数并初始化新对象的一个或多个成员。 |
MemberListBinding | 表示初始化新创建对象的一个集合成员的元素。 |
MemberMemberBinding | 表示初始化新创建对象的一个成员的成员。 |
MethodCallExpression | 表示对静态方法或实例方法的调用。 |
NewArrayExpression | 表示创建一个新数组,并可能初始化该新数组的元素。 |
NewExpression | 表示一个构造函数调用。 |
ParameterExpression | 表示一个命名的参数表达式。 |
TryExpression | 表示一个 try/catch/finally/fault 块。 |
若 lambda 表达式被分配给 Expression<TDelegate>
类型的变量,则编译器可以发射代码以创建表示该 lambda 表达式的表达式树。
C# 编译器只能从表达式 Lambda(或单行 Lambda)生成表达式树。 它无法解析语句 lambda (或多行 lambda)。
如:
1 | // 示例展示如何通过 C# 编译器创建表示 Lambda 表达式 num => num < 5 的表达式树 |
Expression<TDelegate>
是直接继承自 LambdaExpression
。
1 | public sealed class Expression<TDelegate> : LambdaExpression |
通过 API 创建表达式树需要使用 Expression
类。类包含创建特定类型表达式树节点的静态工厂方法,比如表示参数变量的 ParameterExpression
,或者是表示方法调用的 MethodCallExpression
。 ParameterExpression
名称空间还解释了 MethodCallExpression
、System.Linq.Expressions
和另一种具体表达式类型。 这些类型来源于抽象类型 Expression
。
使用 API 创建表示 Lambda 表达式 num => num < 5 的表达式树:
1 | using System.Linq.Expressions; |
在 .NET Framework 4 或更高版本中,表达式树 API 还支持赋值表达式和控制流表达式,例如循环、条件块和 try-catch
块等。下列示例展示如何创建计算数字阶乘的表达式树。
1 | // Creating a parameter expression. |
下列代码示例展示如何分解表示 Lambda 表达式 num => num < 5 的表达式树。
1 | // Add the following using directive to your code file: |
Expression<TDelegate>
类型提供了 Compile 方法以将表达式树表示的代码编译成可执行委托。
下列代码示例展示如何编译表达式树并运行结果代码。
1 | // Creating an expression tree. |
Lambda 表达式的表达式树的类型为 LambdaExpression
或 Expression<TDelegate>
。 若要执行这些表达式树,调用 Compile 方法来创建一个可执行的委托,然后调用该委托。
备注:
如果委托的类型未知,也就是说 Lambda 表达式的类型为 LambdaExpression
,而不是 Expression<TDelegate>
,则必须对委托调用 DynamicInvoke
方法,而不是直接调用委托。如 lambdaExpr.Compile().DynamicInvoke(1)
;
下面的代码示例演示如何通过创建 lambda 表达式并执行它来执行代表幂运算的表达式树。
1 | // The expression tree to execute. |
表达式树是不可变的,这意味着不能直接对它们进行修改。 若要更改表达式树,必须创建现有表达式树的副本,创建此副本后,进行必要的更改。 可以使用 ExpressionVisitor
类遍历现有表达式树,以及复制它访问的每个节点。
运算从条件 AND
更改为条件 OR
。
1 | private static void Main(string[] args) |
在 LINQ 中,表达式树用于表示针对数据源的结构化查询,这些数据源可实现 IQueryable<T>
。 例如,LINQ 提供程序可实现 IQueryable<T>
接口,用于查询关系数据存储。 C# 编译器将针对此类数据源的查询编译为代码,该代码在运行时会生成一个表达式树。 然后,查询提供程序可以遍历表达式树数据结构,并将其转换为适合于数据源的查询语言。
表达式树还可以用在 LINQ 中,用于表示分配给类型为 Expression<TDelegate>
的变量的 lambda 表达式。
下面的示例演示如何使用表达式树依据 IQueryable
数据源构造一个查询,然后执行该查询。 代码将生成一个表达式树来表示以下查询:
1 | companies.Where(company => (company.ToLower() == "coho winery" || company.Length > 16)).OrderBy(company => company) |
System.Linq.Expressions
命名空间中的工厂方法用于创建表达式树,这些表达式树表示构成总体查询的表达式。 表示标准查询运算符方法调用的表达式将引用这些方法的 Queryable
实现。 最终的表达式树将传递给 IQueryable
数据源的提供程序的 CreateQuery<TElement>(Expression)
实现,以创建 IQueryable
类型的可执行查询。 通过枚举该查询变量获得结果。
1 | // Add a using directive for System.Linq.Expressions. |