CSharp-CodeDom创建XML文档

可使用 CodeDOM 创建生成 XML 文档的代码。 该进程包括创建包含 XML 文档注释的 CodeDOM 图、生成代码和通过创建 XML 文档输出的编译器选项编译生成的代码。

参考:如何:使用 CodeDOM 创建 XML 文档文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
namespace ConsoleApp1
{
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Text.RegularExpressions;

class Program
{
static string providerName = "cs";
static string sourceFileName = "test.cs";
static void Main(string[] args)
{
CodeDomProvider provider =
CodeDomProvider.CreateProvider(providerName);

LogMessage("Building CodeDOM graph...");

CodeCompileUnit cu = new CodeCompileUnit();

cu = BuildHelloWorldGraph();

StreamWriter sourceFile = new StreamWriter(sourceFileName);
provider.GenerateCodeFromCompileUnit(cu, sourceFile, null);
sourceFile.Close();

CompilerParameters opt = new CompilerParameters(new string[]{
"System.dll" });
opt.GenerateExecutable = true;
opt.OutputAssembly = "HelloWorld.exe";
opt.TreatWarningsAsErrors = true;
opt.IncludeDebugInformation = true;
opt.GenerateInMemory = true;
opt.CompilerOptions = "/doc:HelloWorldDoc.xml";

CompilerResults results;

LogMessage("Compiling with " + providerName);
results = provider.CompileAssemblyFromFile(opt, sourceFileName);

OutputResults(results);
if (results.NativeCompilerReturnValue != 0)
{
LogMessage("");
LogMessage("Compilation failed.");
}
else
{
LogMessage("");
LogMessage("Demo completed successfully.");
}
//File.Delete(sourceFileName);

Console.ReadKey();
}

// Build a Hello World program graph using
// System.CodeDom types.
public static CodeCompileUnit BuildHelloWorldGraph()
{
// Create a new CodeCompileUnit to contain
// the program graph.
CodeCompileUnit compileUnit = new CodeCompileUnit();

// Declare a new namespace called Samples.
CodeNamespace samples = new CodeNamespace("Samples");
// Add the new namespace to the compile unit.
compileUnit.Namespaces.Add(samples);

// Add the new namespace import for the System namespace.
samples.Imports.Add(new CodeNamespaceImport("System"));

// Declare a new type called Class1.
CodeTypeDeclaration class1 = new CodeTypeDeclaration("Class1");

class1.Comments.Add(new CodeCommentStatement("<summary>", true));
class1.Comments.Add(new CodeCommentStatement(
"Create a Hello World application.", true));
class1.Comments.Add(new CodeCommentStatement(
@"<seealso cref=" + '"' + "Class1.Main" + '"' + "/>", true));
class1.Comments.Add(new CodeCommentStatement("</summary>", true));

// Add the new type to the namespace type collection.
samples.Types.Add(class1);

// Declare a new code entry point method.
CodeEntryPointMethod start = new CodeEntryPointMethod();
start.Comments.Add(new CodeCommentStatement("<summary>", true));
start.Comments.Add(new CodeCommentStatement(
"Main method for HelloWorld application.", true));
start.Comments.Add(new CodeCommentStatement(
@"<para>Add a new paragraph to the description.</para>", true));
start.Comments.Add(new CodeCommentStatement("</summary>", true));

// Create a type reference for the System.Console class.
CodeTypeReferenceExpression csSystemConsoleType =
new CodeTypeReferenceExpression("System.Console");

// Build a Console.WriteLine statement.
CodeMethodInvokeExpression cs1 = new CodeMethodInvokeExpression(
csSystemConsoleType, "WriteLine",
new CodePrimitiveExpression("Hello World!"));

// Add the WriteLine call to the statement collection.
start.Statements.Add(cs1);

// Build another Console.WriteLine statement.
CodeMethodInvokeExpression cs2 = new CodeMethodInvokeExpression(
csSystemConsoleType, "WriteLine", new CodePrimitiveExpression(
"Press the ENTER key to continue."));

// Add the WriteLine call to the statement collection.
start.Statements.Add(cs2);

// Build a call to System.Console.ReadLine.
CodeMethodInvokeExpression csReadLine =
new CodeMethodInvokeExpression(csSystemConsoleType, "ReadLine");

// Add the ReadLine statement.
start.Statements.Add(csReadLine);

// Add the code entry point method to
// the Members collection of the type.
class1.Members.Add(start);

return compileUnit;
}
static void LogMessage(string text)
{
Console.WriteLine(text);
}

static void OutputResults(CompilerResults results)
{
LogMessage("NativeCompilerReturnValue=" +
results.NativeCompilerReturnValue.ToString());
foreach (string s in results.Output)
{
LogMessage(s);
}
}
}
}

CSharp-CodeDom生成和编译源代码

System.CodeDom.Compiler 命名空间提供了从 CodeDOM 对象图生成源代码和用受支持的编译器管理编译的接口。 代码提供程序可根据 CodeDOM 图,用某种编程语言生成源代码。 从 CodeDomProvider 派生的类通常可以提供一些方法,用于生成和编译提供程序支持的语言代码。

参考:

在 CodeDOM 图中生成和编译源代码

CodeDomProvider Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Microsoft.CSharp;
using Microsoft.VisualBasic;
using Microsoft.JScript;

// This example demonstrates building a Hello World program graph
// using System.CodeDom elements. It calls code generator and
// code compiler methods to build the program using CSharp, VB, or
// JScript. A Windows Forms interface is included. Note: Code
// must be compiled and linked with the Microsoft.JScript assembly.
namespace WindowsFormsApp1
{
class CodeDomExample
{
// Build a Hello World program graph using
// System.CodeDom types.
public static CodeCompileUnit BuildHelloWorldGraph()
{
// Create a new CodeCompileUnit to contain
// the program graph.
CodeCompileUnit compileUnit = new CodeCompileUnit();

// Declare a new namespace called Samples.
CodeNamespace samples = new CodeNamespace("Samples");
// Add the new namespace to the compile unit.
compileUnit.Namespaces.Add(samples);

// Add the new namespace import for the System namespace.
samples.Imports.Add(new CodeNamespaceImport("System"));

// Declare a new type called Class1.
CodeTypeDeclaration class1 = new CodeTypeDeclaration("Class1");
// Add the new type to the namespace type collection.
samples.Types.Add(class1);

// Declare a new code entry point method.
CodeEntryPointMethod start = new CodeEntryPointMethod();

// Create a type reference for the System.Console class.
CodeTypeReferenceExpression csSystemConsoleType = new CodeTypeReferenceExpression("System.Console");

// Build a Console.WriteLine statement.
CodeMethodInvokeExpression cs1 = new CodeMethodInvokeExpression(
csSystemConsoleType, "WriteLine",
new CodePrimitiveExpression("Hello World!"));

// Add the WriteLine call to the statement collection.
start.Statements.Add(cs1);

// Build another Console.WriteLine statement.
CodeMethodInvokeExpression cs2 = new CodeMethodInvokeExpression(
csSystemConsoleType, "WriteLine",
new CodePrimitiveExpression("Press the Enter key to continue."));

// Add the WriteLine call to the statement collection.
start.Statements.Add(cs2);

// Build a call to System.Console.ReadLine.
CodeMethodInvokeExpression csReadLine = new CodeMethodInvokeExpression(
csSystemConsoleType, "ReadLine");

// Add the ReadLine statement.
start.Statements.Add(csReadLine);

// Add the code entry point method to
// the Members collection of the type.
class1.Members.Add(start);

return compileUnit;
}

public static void GenerateCode(CodeDomProvider provider,
CodeCompileUnit compileunit)
{
// Build the source file name with the appropriate
// language extension.
String sourceFile;
if (provider.FileExtension[0] == '.')
{
sourceFile = "TestGraph" + provider.FileExtension;
}
else
{
sourceFile = "TestGraph." + provider.FileExtension;
}

// Create an IndentedTextWriter, constructed with
// a StreamWriter to the source file.
IndentedTextWriter tw = new IndentedTextWriter(new StreamWriter(sourceFile, false), " ");
// Generate source code using the code generator.
provider.GenerateCodeFromCompileUnit(compileunit, tw, new CodeGeneratorOptions());
// Close the output file.
tw.Close();
}

public static CompilerResults CompileCode(CodeDomProvider provider,
String sourceFile,
String exeFile)
{
// Configure a CompilerParameters that links System.dll
// and produces the specified executable file.
String[] referenceAssemblies = { "System.dll" };
CompilerParameters cp = new CompilerParameters(referenceAssemblies,
exeFile, false);
// Generate an executable rather than a DLL file.
cp.GenerateExecutable = true;

// Invoke compilation.
CompilerResults cr = provider.CompileAssemblyFromFile(cp, sourceFile);
// Return the results of compilation.
return cr;
}
}

public class CodeDomExampleForm : System.Windows.Forms.Form
{
private System.Windows.Forms.Button run_button = new System.Windows.Forms.Button();
private System.Windows.Forms.Button compile_button = new System.Windows.Forms.Button();
private System.Windows.Forms.Button generate_button = new System.Windows.Forms.Button();
private System.Windows.Forms.TextBox textBox1 = new System.Windows.Forms.TextBox();
private System.Windows.Forms.ComboBox comboBox1 = new System.Windows.Forms.ComboBox();
private System.Windows.Forms.Label label1 = new System.Windows.Forms.Label();

private void generate_button_Click(object sender, System.EventArgs e)
{
CodeDomProvider provider = GetCurrentProvider();
CodeDomExample.GenerateCode(provider, CodeDomExample.BuildHelloWorldGraph());

// Build the source file name with the appropriate
// language extension.
String sourceFile;
if (provider.FileExtension[0] == '.')
{
sourceFile = "TestGraph" + provider.FileExtension;
}
else
{
sourceFile = "TestGraph." + provider.FileExtension;
}

// Read in the generated source file and
// display the source text.
StreamReader sr = new StreamReader(sourceFile);
textBox1.Text = sr.ReadToEnd();
sr.Close();
}

private void compile_button_Click(object sender, System.EventArgs e)
{
CodeDomProvider provider = GetCurrentProvider();

// Build the source file name with the appropriate
// language extension.
String sourceFile;
if (provider.FileExtension[0] == '.')
{
sourceFile = "TestGraph" + provider.FileExtension;
}
else
{
sourceFile = "TestGraph." + provider.FileExtension;
}

// Compile the source file into an executable output file.
CompilerResults cr = CodeDomExample.CompileCode(provider,
sourceFile,
"TestGraph.exe");

if (cr.Errors.Count > 0)
{
// Display compilation errors.
textBox1.Text = "Errors encountered while building " +
sourceFile + " into " + cr.PathToAssembly + ": \r\n\n";
foreach (CompilerError ce in cr.Errors)
textBox1.AppendText(ce.ToString() + "\r\n");
run_button.Enabled = false;
}
else
{
textBox1.Text = "Source " + sourceFile + " built into " +
cr.PathToAssembly + " with no errors.";
run_button.Enabled = true;
}
}

private void run_button_Click(object sender,
System.EventArgs e)
{
Process.Start("TestGraph.exe");
}

private CodeDomProvider GetCurrentProvider()
{
CodeDomProvider provider;
switch ((string)this.comboBox1.SelectedItem)
{
case "CSharp":
provider = CodeDomProvider.CreateProvider("CSharp");
break;
case "Visual Basic":
provider = CodeDomProvider.CreateProvider("VisualBasic");
break;
case "JScript":
provider = CodeDomProvider.CreateProvider("JScript");
break;
default:
provider = CodeDomProvider.CreateProvider("CSharp");
break;
}
return provider;
}

public CodeDomExampleForm()
{
this.SuspendLayout();
// Set properties for label1
this.label1.Location = new System.Drawing.Point(395, 20);
this.label1.Size = new Size(180, 22);
this.label1.Text = "Select a programming language:";
// Set properties for comboBox1
this.comboBox1.Location = new System.Drawing.Point(560, 16);
this.comboBox1.Size = new Size(190, 23);
this.comboBox1.Name = "comboBox1";
this.comboBox1.Items.AddRange(new string[] { "CSharp", "Visual Basic", "JScript" });
this.comboBox1.Anchor = System.Windows.Forms.AnchorStyles.Left
| System.Windows.Forms.AnchorStyles.Right
| System.Windows.Forms.AnchorStyles.Top;
this.comboBox1.SelectedIndex = 0;
// Set properties for generate_button.
this.generate_button.Location = new System.Drawing.Point(8, 16);
this.generate_button.Name = "generate_button";
this.generate_button.Size = new System.Drawing.Size(120, 23);
this.generate_button.Text = "Generate Code";
this.generate_button.Click += new System.EventHandler(this.generate_button_Click);
// Set properties for compile_button.
this.compile_button.Location = new System.Drawing.Point(136, 16);
this.compile_button.Name = "compile_button";
this.compile_button.Size = new System.Drawing.Size(120, 23);
this.compile_button.Text = "Compile";
this.compile_button.Click += new System.EventHandler(this.compile_button_Click);
// Set properties for run_button.
this.run_button.Enabled = false;
this.run_button.Location = new System.Drawing.Point(264, 16);
this.run_button.Name = "run_button";
this.run_button.Size = new System.Drawing.Size(120, 23);
this.run_button.Text = "Run";
this.run_button.Click += new System.EventHandler(this.run_button_Click);
// Set properties for textBox1.
this.textBox1.Anchor = (System.Windows.Forms.AnchorStyles.Top
| System.Windows.Forms.AnchorStyles.Bottom
| System.Windows.Forms.AnchorStyles.Left
| System.Windows.Forms.AnchorStyles.Right);
this.textBox1.Location = new System.Drawing.Point(8, 48);
this.textBox1.Multiline = true;
this.textBox1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(744, 280);
this.textBox1.Text = "";
// Set properties for the CodeDomExampleForm.
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(768, 340);
this.MinimumSize = new System.Drawing.Size(750, 340);
this.Controls.AddRange(new System.Windows.Forms.Control[] {this.textBox1,
this.run_button, this.compile_button, this.generate_button,
this.comboBox1, this.label1 });
this.Name = "CodeDomExampleForm";
this.Text = "CodeDom Hello World Example";
this.ResumeLayout(false);
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
}

[STAThread]
static void Main()
{
Application.Run(new CodeDomExampleForm());
}
}
}

CSharp-CodeDOM生成类

CodeDOM:代码文档对象模型。

参考:动态源代码生成和编译

.NET Framework 包括一个名为代码文档对象模型 (CodeDOM) 的机制。通过该机制,发出源代码的程序开发者可基于一种表示要呈现的代码的单一模型,在运行时以多种编程语言生成源代码。
为表示源代码,CodeDOM 元素相互链接,构成一个名为 CodeDOM 图的数据结构。该数据结构对某些源代码的结构建模。

System.CodeDom 命名空间定义某些类型,这些类型可以表示源代码的逻辑结构,且不依赖特定编程语言。 System.CodeDom.Compiler 命名空间定义某些类型,用于从 CodeDOM 图中生成源代码,并管理使用支持语言的源代码的编译工作。 编译器供应商或开发人员可以扩展支持语言。

.NET Framework 包括适用于 CSharpCodeProviderJScriptCodeProviderVBCodeProvider 的代码生成器和代码编译器。

使用CodeDOM创建类

CodeDOM 提供表示多种常见源代码元素的类型。 可以设计一个程序,它使用 CodeDOM 元素生成源代码模型来组合对象图。 对于支持的编程语言,可使用 CodeDOM 代码生成器将此对象图呈现为源代码。 还可使用 CodeDOM 将源代码编译为二进制程序集。

CodeDOM 的常见用途包括:

  • 模板化代码生成:生成适用于 ASP.NET、XML Web 服务客户端代理、代码向导、设计器或其他代码发出机制的代码。
  • 动态编译:支持一种或多种语言的代码编译。

参考:使用 CodeDom

System.CodeDom 命名空间提供用于表示源代码逻辑结构的类,独立于语言语法。

CodeDOM 图的结构类似于一个容器树。 每个可编译 CodeDOM 图的顶端容器或根部容器是 CodeCompileUnit。 源代码模型中的每个元素都必须通过图中 CodeObject 的属性链接至该图。

CodeDOM 定义名为 CodeCompileUnit 的对象,可引用 CodeDOM 对象图,该对象图塑造要编译的源代码的模型。 CodeCompileUnit 具有属性可用于存储对特性、命名空间和程序集的引用。

派生自 CodeDomProvider 类的 CodeDom 提供程序包含处理 CodeCompileUnit 所引用的对象图的方法。

详细请参考:如何:使用 CodeDOM 创建类

CodeStatement Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
namespace ConsoleApp1
{
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;

/// <summary>
/// This code example creates a graph using a CodeCompileUnit and
/// generates source code for the graph using the CSharpCodeProvider.
/// </summary>
internal class Sample
{
/// <summary>
/// Define the compile unit to use for code generation.
/// </summary>
private CodeCompileUnit targetUnit;

/// <summary>
/// The only class in the compile unit. This class contains 2 fields,
/// 3 properties, a constructor, an entry point, and 1 simple method.
/// </summary>
private CodeTypeDeclaration targetClass;

/// <summary>
/// The name of the file to contain the source code.
/// </summary>
private const string outputFileName = "SampleCode.cs";

/// <summary>
/// Define the class.
/// </summary>
public Sample()
{
// 创建编译单元
targetUnit = new CodeCompileUnit();

// 定义命名空间
CodeNamespace samples = new CodeNamespace("CodeDOMSample");

// 导入命名空间
samples.Imports.Add(new CodeNamespaceImport("System"));

// 定义类型
targetClass = new CodeTypeDeclaration("CodeDOMCreatedClass");
targetClass.IsClass = true;
targetClass.TypeAttributes =
TypeAttributes.Public | TypeAttributes.Sealed;

// 向命名空间添加类型
samples.Types.Add(targetClass);

// 将代码元素链接至对象图
targetUnit.Namespaces.Add(samples);
}

/// <summary>
/// Adds two fields to the class.
/// </summary>
public void AddFields()
{
// Declare the widthValue field.
CodeMemberField widthValueField = new CodeMemberField();
widthValueField.Attributes = MemberAttributes.Private;
widthValueField.Name = "widthValue";
widthValueField.Type = new CodeTypeReference(typeof(System.Double));
widthValueField.Comments.Add(new CodeCommentStatement(
"The width of the object."));
targetClass.Members.Add(widthValueField);

// Declare the heightValue field
CodeMemberField heightValueField = new CodeMemberField();
heightValueField.Attributes = MemberAttributes.Private;
heightValueField.Name = "heightValue";
heightValueField.Type =
new CodeTypeReference(typeof(System.Double));
heightValueField.Comments.Add(new CodeCommentStatement(
"The height of the object."));
targetClass.Members.Add(heightValueField);
}

/// <summary>
/// Add three properties to the class.
/// </summary>
public void AddProperties()
{
// Declare the read-only Width property.
CodeMemberProperty widthProperty = new CodeMemberProperty();
widthProperty.Attributes =
MemberAttributes.Public | MemberAttributes.Final;
widthProperty.Name = "Width";
widthProperty.HasGet = true;
widthProperty.Type = new CodeTypeReference(typeof(System.Double));
widthProperty.Comments.Add(new CodeCommentStatement(
"The Width property for the object."));
widthProperty.GetStatements.Add(new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), "widthValue")));
targetClass.Members.Add(widthProperty);

// Declare the read-only Height property.
CodeMemberProperty heightProperty = new CodeMemberProperty();
heightProperty.Attributes =
MemberAttributes.Public | MemberAttributes.Final;
heightProperty.Name = "Height";
heightProperty.HasGet = true;
heightProperty.Type = new CodeTypeReference(typeof(System.Double));
heightProperty.Comments.Add(new CodeCommentStatement(
"The Height property for the object."));
heightProperty.GetStatements.Add(new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), "heightValue")));
targetClass.Members.Add(heightProperty);

// Declare the read only Area property.
CodeMemberProperty areaProperty = new CodeMemberProperty();
areaProperty.Attributes =
MemberAttributes.Public | MemberAttributes.Final;
areaProperty.Name = "Area";
areaProperty.HasGet = true;
areaProperty.Type = new CodeTypeReference(typeof(System.Double));
areaProperty.Comments.Add(new CodeCommentStatement(
"The Area property for the object."));

// Create an expression to calculate the area for the get accessor
// of the Area property.
CodeBinaryOperatorExpression areaExpression =
new CodeBinaryOperatorExpression(
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), "widthValue"),
CodeBinaryOperatorType.Multiply,
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), "heightValue"));
areaProperty.GetStatements.Add(
new CodeMethodReturnStatement(areaExpression));
targetClass.Members.Add(areaProperty);
}

/// <summary>
/// Adds a method to the class. This method multiplies values stored
/// in both fields.
/// </summary>
public void AddMethod()
{
// Declaring a ToString method
CodeMemberMethod toStringMethod = new CodeMemberMethod();
toStringMethod.Attributes =
MemberAttributes.Public | MemberAttributes.Override;
toStringMethod.Name = "ToString";
toStringMethod.ReturnType =
new CodeTypeReference(typeof(System.String));

CodeFieldReferenceExpression widthReference =
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), "Width");
CodeFieldReferenceExpression heightReference =
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), "Height");
CodeFieldReferenceExpression areaReference =
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), "Area");

// Declaring a return statement for method ToString.
CodeMethodReturnStatement returnStatement =
new CodeMethodReturnStatement();

// This statement returns a string representation of the width,
// height, and area.
string formattedOutput = "The object:" + Environment.NewLine +
" width = {0}," + Environment.NewLine +
" height = {1}," + Environment.NewLine +
" area = {2}";
returnStatement.Expression =
new CodeMethodInvokeExpression(
new CodeTypeReferenceExpression("System.String"), "Format",
new CodePrimitiveExpression(formattedOutput),
widthReference, heightReference, areaReference);
toStringMethod.Statements.Add(returnStatement);
targetClass.Members.Add(toStringMethod);
}

/// <summary>
/// Add a constructor to the class.
/// </summary>
public void AddConstructor()
{
// Declare the constructor
CodeConstructor constructor = new CodeConstructor();
constructor.Attributes =
MemberAttributes.Public | MemberAttributes.Final;

// Add parameters.
constructor.Parameters.Add(new CodeParameterDeclarationExpression(
typeof(System.Double), "width"));
constructor.Parameters.Add(new CodeParameterDeclarationExpression(
typeof(System.Double), "height"));

// Add field initialization logic
CodeFieldReferenceExpression widthReference =
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), "widthValue");
constructor.Statements.Add(new CodeAssignStatement(widthReference,
new CodeArgumentReferenceExpression("width")));
CodeFieldReferenceExpression heightReference =
new CodeFieldReferenceExpression(
new CodeThisReferenceExpression(), "heightValue");
constructor.Statements.Add(new CodeAssignStatement(heightReference,
new CodeArgumentReferenceExpression("height")));
targetClass.Members.Add(constructor);
}

/// <summary>
/// Add an entry point to the class.
/// </summary>
public void AddEntryPoint()
{
// 为可执行程序定义代码入口点方法
CodeEntryPointMethod start = new CodeEntryPointMethod();
CodeObjectCreateExpression objectCreate =
new CodeObjectCreateExpression(
new CodeTypeReference("CodeDOMCreatedClass"),
new CodePrimitiveExpression(5.3),
new CodePrimitiveExpression(6.9));

// Add the statement:
// "CodeDOMCreatedClass testClass =
// new CodeDOMCreatedClass(5.3, 6.9);"
start.Statements.Add(new CodeVariableDeclarationStatement(
new CodeTypeReference("CodeDOMCreatedClass"), "testClass",
objectCreate));

// Creat the expression:
// "testClass.ToString()"
CodeMethodInvokeExpression toStringInvoke =
new CodeMethodInvokeExpression(
new CodeVariableReferenceExpression("testClass"), "ToString");

// Add a System.Console.WriteLine statement with the previous
// expression as a parameter.
start.Statements.Add(new CodeMethodInvokeExpression(
new CodeTypeReferenceExpression("System.Console"),
"WriteLine", toStringInvoke));
targetClass.Members.Add(start);
}

/// <summary>
/// Generate CSharp source code from the compile unit.
/// </summary>
/// <param name="filename">Output file name</param>
public void GenerateCSharpCode(string fileName)
{
CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
CodeGeneratorOptions options = new CodeGeneratorOptions();
options.BracingStyle = "C";
using (StreamWriter sourceWriter = new StreamWriter(fileName))
{
provider.GenerateCodeFromCompileUnit(
targetUnit, sourceWriter, options);
}
}

/// <summary>
/// Create the CodeDOM graph and generate the code.
/// </summary>
private static void Main()
{
Sample sample = new Sample();
sample.AddFields();
sample.AddProperties();
sample.AddMethod();
sample.AddConstructor();
sample.AddEntryPoint();
sample.GenerateCSharpCode(outputFileName);
}
}
}

生成类的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//------------------------------------------------------------------------------
// <auto-generated>
// 此代码由工具生成。
// 运行时版本:4.0.30319.42000
//
// 对此文件的更改可能会导致不正确的行为,并且如果
// 重新生成代码,这些更改将会丢失。
// </auto-generated>
//------------------------------------------------------------------------------

namespace CodeDOMSample
{
using System;


public sealed class CodeDOMCreatedClass
{

// The width of the object.
private double widthValue;

// The height of the object.
private double heightValue;

public CodeDOMCreatedClass(double width, double height)
{
this.widthValue = width;
this.heightValue = height;
}

// The Width property for the object.
public double Width
{
get
{
return this.widthValue;
}
}

// The Height property for the object.
public double Height
{
get
{
return this.heightValue;
}
}

// The Area property for the object.
public double Area
{
get
{
return (this.widthValue * this.heightValue);
}
}

public override string ToString()
{
return string.Format("The object:\r\n width = {0},\r\n height = {1},\r\n area = {2}", this.Width, this.Height, this.Area);
}

public static void Main()
{
CodeDOMCreatedClass testClass = new CodeDOMCreatedClass(5.3D, 6.9D);
System.Console.WriteLine(testClass.ToString());
}
}
}

某方法只要属于泛型类型,且使用该类型的类型参数,就不是泛型方法。 只有当方法有属于自己的类型参数列表时才是泛型方法。

参考:用反射发出定义泛型方法

动态类型生成的可回收程序集

如果发出对泛型类型方法的调用,并且这些类型的类型参数是泛型方法的类型参数,则必须使用 TypeBuilder 类的 staticGetConstructor(Type, ConstructorInfo)、GetMethod(Type, MethodInfo) 和 GetField(Type, FieldInfo) 方法重载来获取方法的构造窗体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
namespace ConsoleApp1
{
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

// Declare a generic delegate that can be used to execute the
// finished method.
//
public delegate TOut D<TIn, TOut>(TIn[] input);

class GenericMethodBuilder
{
// This method shows how to declare, in Visual Basic, the generic
// method this program emits. The method has two type parameters,
// TInput and TOutput, the second of which must be a reference type
// (class), must have a parameterless constructor (new()), and must
// implement ICollection<TInput>. This interface constraint
// ensures that ICollection<TInput>.Add can be used to add
// elements to the TOutput object the method creates. The method
// has one formal parameter, input, which is an array of TInput.
// The elements of this array are copied to the new TOutput.
// 1,该方法具有两个类型参数:TInput 和 TOutput。
// 其中第二个参数必须具备以下条件:是引用类型 (class);具有无参数构造函数 (new);
// 实现 ICollection(Of TInput)(在 C# 中为 ICollection<TInput>)。
// 此接口约束确保可使用 ICollection<T>.Add 方法将元素添加到该方法
// 创建的 TOutput 集合。 该方法具有一个形参 input,它是 TInput 的数组。
// 该方法将创建一个 TOutput 类型的集合,并将 input 的元素复制到该集合。
public static TOutput Factory<TInput, TOutput>(TInput[] tarray)
where TOutput : class, ICollection<TInput>, new()
{
TOutput ret = new TOutput();
ICollection<TInput> ic = ret;

foreach (TInput t in tarray)
{
ic.Add(t);
}
return ret;
}

public static void Main()
{
// The following shows the usage syntax of the C#
// version of the generic method emitted by this program.
// Note that the generic parameters must be specified
// explicitly, because the compiler does not have enough
// context to infer the type of TOutput. In this case, TOutput
// is a generic List containing strings.
//
string[] arr = { "a", "b", "c", "d", "e" };
List<string> list1 =
GenericMethodBuilder.Factory<string, List<string>>(arr);
Console.WriteLine("The first element is: {0}", list1[0]);


// Creating a dynamic assembly requires an AssemblyName
// object, and the current application domain.
// 2,定义动态程序集和动态模块,以包含泛型方法所属类型。
AssemblyName asmName = new AssemblyName("DemoMethodBuilder1");
AppDomain domain = AppDomain.CurrentDomain;
AssemblyBuilder demoAssembly =
domain.DefineDynamicAssembly(asmName,
AssemblyBuilderAccess.RunAndSave);

// Define the module that contains the code. For an
// assembly with one module, the module name is the
// assembly name plus a file extension.
ModuleBuilder demoModule =
demoAssembly.DefineDynamicModule(asmName.Name,
asmName.Name + ".dll");

// Define a type to contain the method.
// 3,定义泛型方法所属类型。 该类型不一定是泛型类型。
// 泛型方法可以属于泛型或非泛型类型。
TypeBuilder demoType =
demoModule.DefineType("DemoType", TypeAttributes.Public);

// Define a public static method with standard calling
// conventions. Do not specify the parameter types or the
// return type, because type parameters will be used for
// those types, and the type parameters have not been
// defined yet.
// 4,定义泛型方法。
MethodBuilder factory =
demoType.DefineMethod("Factory",
MethodAttributes.Public | MethodAttributes.Static);

// Defining generic type parameters for the method makes it a
// generic method. To make the code easier to read, each
// type parameter is copied to a variable of the same name.
// 4,通过将包含参数名称的字符串数组传递给 MethodBuilder.DefineGenericParameters
// 方法来定义 DemoMethod 的泛型类型参数。 这使该方法成为泛型方法。
string[] typeParameterNames = { "TInput", "TOutput" };
GenericTypeParameterBuilder[] typeParameters =
factory.DefineGenericParameters(typeParameterNames);

GenericTypeParameterBuilder TInput = typeParameters[0];
GenericTypeParameterBuilder TOutput = typeParameters[1];

// Add special constraints.
// The type parameter TOutput is constrained to be a reference
// type, and to have a parameterless constructor. This ensures
// that the Factory method can create the collection type.
// 6,可以选择向类型参数添加特殊约束。
// 使用 SetGenericParameterAttributes 方法添加特殊约束。
// 约束 TOutput 作为引用类型并且具有无参数构造函数。
TOutput.SetGenericParameterAttributes(
GenericParameterAttributes.ReferenceTypeConstraint |
GenericParameterAttributes.DefaultConstructorConstraint);

// Add interface and base type constraints.
// The type parameter TOutput is constrained to types that
// implement the ICollection<T> interface, to ensure that
// they have an Add method that can be used to add elements.
//
// To create the constraint, first use MakeGenericType to bind
// the type parameter TInput to the ICollection<T> interface,
// returning the type ICollection<TInput>, then pass
// the newly created type to the SetInterfaceConstraints
// method. The constraints must be passed as an array, even if
// there is only one interface.
// 7,可以选择向类型参数添加类约束和接口约束。
// 约束类型参数 TOutput 为实现 ICollection<TInput>)接口的类型。
Type icoll = typeof(ICollection<>);
Type icollOfTInput = icoll.MakeGenericType(TInput);
Type[] constraints = { icollOfTInput };
TOutput.SetInterfaceConstraints(constraints);

// Set parameter types for the method. The method takes
// one parameter, an array of type TInput.
// 8,使用 SetParameters 方法定义方法的形参。
Type[] parms = { TInput.MakeArrayType() };
factory.SetParameters(parms);

// Set the return type for the method. The return type is
// the generic type parameter TOutput.
// 9,使用 SetReturnType 方法定义该方法的返回类型。
factory.SetReturnType(TOutput);

// Generate a code body for the method.
// -----------------------------------
// Get a code generator and declare local variables and
// labels. Save the input array to a local variable.
// 10,使用 ILGenerator 发出方法主体。

// (1),获取代码生成器,并声明局部变量和标签。
ILGenerator ilgen = factory.GetILGenerator();

//用于保留该方法返回的新 TOutput
LocalBuilder retVal = ilgen.DeclareLocal(TOutput);

//用于在 TOutput 强制转换成 ICollection<TInput> 时进行保留;
LocalBuilder ic = ilgen.DeclareLocal(icollOfTInput);

// 用于保留 TInput 对象的输入数组;
LocalBuilder input = ilgen.DeclareLocal(TInput.MakeArrayType());

//用于循环访问数组
LocalBuilder index = ilgen.DeclareLocal(typeof(int));

// 用于进入循环 (enterLoop)
Label enterLoop = ilgen.DefineLabel();

//用于循环的顶部 (loopAgain)
Label loopAgain = ilgen.DefineLabel();

ilgen.Emit(OpCodes.Ldarg_0);
ilgen.Emit(OpCodes.Stloc_S, input);//将参数存储到局部变量 input

// Create an instance of TOutput, using the generic method
// overload of the Activator.CreateInstance method.
// Using this overload requires the specified type to have
// a parameterless constructor, which is the reason for adding
// that constraint to TOutput. Create the constructed generic
// method by passing TOutput to MakeGenericMethod. After
// emitting code to call the method, emit code to store the
// new TOutput in a local variable.
// (2),使用 Activator.CreateInstance 方法的泛型方法重载,
// 发出代码,创建 TOutput 的实例。
// TOutput ret = new TOutput();
MethodInfo createInst =
typeof(Activator).GetMethod("CreateInstance", Type.EmptyTypes);
MethodInfo createInstOfTOutput =
createInst.MakeGenericMethod(TOutput);// 创建构造泛型方法

ilgen.Emit(OpCodes.Call, createInstOfTOutput);
//发出代码调用方法后,将其存储在局部变量 retVal
ilgen.Emit(OpCodes.Stloc_S, retVal);

// Load the reference to the TOutput object, cast it to
// ICollection<TInput>, and save it.
// (3),发出代码,将新的 TOutput 对象强制转换为 ICollection<TInput>,
// 并将其存储在局部变量 ic。
// ICollection<TInput> ic = ret;
ilgen.Emit(OpCodes.Ldloc_S, retVal);
ilgen.Emit(OpCodes.Box, TOutput);
ilgen.Emit(OpCodes.Castclass, icollOfTInput);
ilgen.Emit(OpCodes.Stloc_S, ic);

// Loop through the array, adding each element to the new
// instance of TOutput. Note that in order to get a MethodInfo
// for ICollection<TInput>.Add, it is necessary to first
// get the Add method for the generic type defintion,
// ICollection<T>.Add. This is because it is not possible
// to call GetMethod on icollOfTInput. The static overload of
// TypeBuilder.GetMethod produces the correct MethodInfo for
// the constructed type.
// (4),获取表示 ICollection<T>.Add 方法的 MethodInfo。
MethodInfo mAddPrep = icoll.GetMethod("Add");
MethodInfo mAdd = TypeBuilder.GetMethod(icollOfTInput, mAddPrep);

// Initialize the count and enter the loop.
// (5),通过加载 32 位整数 0 并将其存储在变量中,发出代码,初始化 index 变量。
ilgen.Emit(OpCodes.Ldc_I4_0);
ilgen.Emit(OpCodes.Stloc_S, index); // index=0
ilgen.Emit(OpCodes.Br_S, enterLoop); //发出代码,分支到标签 enterLoop

// Mark the beginning of the loop. Push the ICollection
// reference on the stack, so it will be in position for the
// call to Add. Then push the array and the index on the
// stack, get the array element, and call Add (represented
// by the MethodInfo mAdd) to add it to the collection.
//
// The other ten instructions just increment the index
// and test for the end of the loop. Note the MarkLabel
// method, which sets the point in the code where the
// loop is entered. (See the earlier Br_S to enterLoop.)
// (6),发出该循环的代码。
ilgen.MarkLabel(loopAgain);

ilgen.Emit(OpCodes.Ldloc_S, ic);
ilgen.Emit(OpCodes.Ldloc_S, input); // 数组
ilgen.Emit(OpCodes.Ldloc_S, index); // 索引

// Ldelem 操作码从堆栈中弹出该索引和数组,
// 然后将索引数组元素推入堆栈。
ilgen.Emit(OpCodes.Ldelem, TInput);
// 堆栈现已准备好调用 ICollection<T>.Add 方法,
// 该方法从堆栈中弹出集合和新元素,并将该元素添加到该集合中。
ilgen.Emit(OpCodes.Callvirt, mAdd);

// index+=1;
ilgen.Emit(OpCodes.Ldloc_S, index);
ilgen.Emit(OpCodes.Ldc_I4_1);
ilgen.Emit(OpCodes.Add);
ilgen.Emit(OpCodes.Stloc_S, index);

// 调用 MarkLabel,将该点设置为循环的入口点。
ilgen.MarkLabel(enterLoop);
ilgen.Emit(OpCodes.Ldloc_S, index);// 加载该索引
ilgen.Emit(OpCodes.Ldloc_S, input);// 将输入数组推入堆栈
ilgen.Emit(OpCodes.Ldlen); // 获取其长度
ilgen.Emit(OpCodes.Conv_I4);
ilgen.Emit(OpCodes.Clt); // 二者进行比较
ilgen.Emit(OpCodes.Brtrue_S, loopAgain);

// (7),发出代码,将 TOutput 对象推入堆栈,并从该方法返回。
ilgen.Emit(OpCodes.Ldloc_S, retVal);
ilgen.Emit(OpCodes.Ret);

// Complete the type.
// 11,完成包含该方法的类型,并保存程序集。
Type dt = demoType.CreateType();
// Save the assembly, so it can be examined with Ildasm.exe.
demoAssembly.Save(asmName.Name + ".dll");

/*调用泛型方法*/

// To create a constructed generic method that can be
// executed, first call the GetMethod method on the completed
// type to get the generic method definition. Call MakeGenericType
// on the generic method definition to obtain the constructed
// method, passing in the type arguments. In this case, the
// constructed method has string for TInput and List<string>
// for TOutput.
// 1,分配泛型类型参数
MethodInfo m = dt.GetMethod("Factory");
MethodInfo bound =
m.MakeGenericMethod(typeof(string), typeof(List<string>));

// Display a string representing the bound method.
Console.WriteLine(bound);


// Once the generic method is constructed,
// you can invoke it and pass in an array of objects
// representing the arguments. In this case, there is only
// one element in that array, the argument 'arr'.
// 2,若要以后期绑定的形式调用该方法,请使用 Invoke 方法。
// Factory为静态方法
object o = bound.Invoke(null, new object[] { arr });
List<string> list2 = (List<string>)o;

Console.WriteLine("The first element is: {0}", list2[0]);


// You can get better performance from multiple calls if
// you bind the constructed method to a delegate. The
// following code uses the generic delegate D defined
// earlier.
//
Type dType = typeof(D<string, List<string>>);
D<string, List<string>> test;
test = (D<string, List<string>>)
Delegate.CreateDelegate(dType, bound);

List<string> list3 = test(arr);
Console.WriteLine("The first element is: {0}", list3[0]);

Console.ReadKey();
}
}
}

参考:发出动态方法和程序集

System.Reflection.Emit 命名空间提供的功能被称为反射发出。 System.Reflection.Emit 命名空间中的一组托管类型,它们允许编译器或工具在运行时发出元数据和 Microsoft 中间语言 (MSIL),并在磁盘上生成可移植可执行 (PE) 文件(可选)。

反射发出具有以下功能:

  • 在运行时定义轻量全局方法(使用 DynamicMethod 类)并通过委托执行这些方法。
  • 在运行时定义程序集,然后运行程序集以及/或者将程序集保存到磁盘。
  • 在运行时定义程序集,运行程序集,然后卸载程序集并允许垃圾回收回收其资源。
  • 在运行时在新的程序集中定义模块,然后运行模块以及/或者将模块保存到磁盘。
  • 在运行时在模块中定义类型,创建这些类型的实例并调用其方法。
  • 为已定义模块定义可供工具(如调试器和代码探查器)使用的符号化信息。

参考:

OpCodes

System.Reflection.Emit

CSharp基础-认识反射-下

访问自定义特性

参考:访问自定义特性

仅反射上下文

加载到仅反射上下文中的代码无法执行。在仅反射上下文中加载和检查自定义特性,使用 CustomAttributeData 类。通过使用静态 CustomAttributeData.GetCustomAttributes 方法的相应重载来获取此类的实例。

执行上下文

用于查询执行上下文中的特性的主要反射方法是 MemberInfo.GetCustomAttributesAttribute.GetCustomAttributes

自定义特性的可访问性根据附加该特性的程序集来进行检查。

Assembly.GetCustomAttributes(Boolean) 等方法检查类型参数的可见性和可访问性。 只有包含用户定义类型的程序集中的代码才能使用 GetCustomAttributes 检索该类型的自定义特性。

自定义特性反射模型可能会在定义类型的程序集外泄漏用户定义类型的实例。 为了防止客户端发现关于用户定义的自定义特性类型的信息,请将该类型的成员定义为非公共成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;

public class ExampleAttribute : Attribute
{
private string stringVal;

public ExampleAttribute()
{
stringVal = "This is the default string.";
}

public string StringValue
{
get { return stringVal; }
set { stringVal = value; }
}
}

[Example(StringValue="This is a string.")]
class Class1
{
public static void Main()
{
System.Reflection.MemberInfo info = typeof(Class1);
foreach (object attrib in info.GetCustomAttributes(true))
{
Console.WriteLine(attrib);
}
}
}

指定完全限定的类型名称

参考:指定完全限定的类型名称

CSharp基础-认识反射-中

本文包括动态加载和使用类型,将程序集加载到仅反射上下文中;

动态加载和使用类型

自定义绑定

参考:自定义绑定

反射可在代码中显式用于完成后期绑定。在通过反射实现的后期绑定中,必须由自定义绑定来控制该绑定。 Binder 类提供对成员选择和调用的自定义控制。

使用自定义绑定可以在运行时加载程序集、获取该程序集中有关类型的信息、指定所需类型,并在之后调用该类型上的方法或访问该类型上的字段或属性。 如果在编译时不知道对象的类型,则此方法非常有用,例如当对象类型取决于用户输入时。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
namespace ClassLibrary1
{
public class MySimpleClass
{
public void MyMethod(string str, int i)
{
Console.WriteLine("MyMethod parameters: {0}, {1}", str, i);
}

public void MyMethod(string str, int i, int j)
{
Console.WriteLine("MyMethod parameters: {0}, {1}, {2}",
str, i, j);
}
}
}

using ClassLibrary1;
using System;
using System.Globalization;
using System.Reflection;

namespace ConsoleApp1
{
internal class MyMainClass
{
private static void Main()
{
// Get the type of MySimpleClass.
Type myType = typeof(MySimpleClass);

// Get an instance of MySimpleClass.
MySimpleClass myInstance = new MySimpleClass();
MyCustomBinder myCustomBinder = new MyCustomBinder();

// Get the method information for the particular overload
// being sought.
MethodInfo myMethod = myType.GetMethod("MyMethod",
BindingFlags.Public | BindingFlags.Instance,
myCustomBinder, new Type[] {typeof(string),
typeof(int)}, null);
Console.WriteLine(myMethod.ToString());

// Invoke the overload.
myType.InvokeMember("MyMethod", BindingFlags.InvokeMethod,
myCustomBinder, myInstance,
new Object[] { "Testing...", (int)32 });

Console.ReadKey();
}
}

// ****************************************************
// A simple custom binder that provides no
// argument type conversion.
// ****************************************************
internal class MyCustomBinder : Binder
{
public override MethodBase BindToMethod(
BindingFlags bindingAttr,
MethodBase[] match,
ref object[] args,
ParameterModifier[] modifiers,
CultureInfo culture,
string[] names,
out object state)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
// Arguments are not being reordered.
state = null;
// Find a parameter match and return the first method with
// parameters that match the request.
foreach (MethodBase mb in match)
{
ParameterInfo[] parameters = mb.GetParameters();

if (ParametersMatch(parameters, args))
{
return mb;
}
}
return null;
}

public override FieldInfo BindToField(BindingFlags bindingAttr,
FieldInfo[] match, object value, CultureInfo culture)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
foreach (FieldInfo fi in match)
{
if (fi.GetType() == value.GetType())
{
return fi;
}
}
return null;
}

public override MethodBase SelectMethod(
BindingFlags bindingAttr,
MethodBase[] match,
Type[] types,
ParameterModifier[] modifiers)
{
if (match == null)
{
throw new ArgumentNullException("match");
}

// Find a parameter match and return the first method with
// parameters that match the request.
foreach (MethodBase mb in match)
{
ParameterInfo[] parameters = mb.GetParameters();
if (ParametersMatch(parameters, types))
{
return mb;
}
}

return null;
}

public override PropertyInfo SelectProperty(
BindingFlags bindingAttr,
PropertyInfo[] match,
Type returnType,
Type[] indexes,
ParameterModifier[] modifiers)
{
if (match == null)
{
throw new ArgumentNullException("match");
}
foreach (PropertyInfo pi in match)
{
if (pi.GetType() == returnType &&
ParametersMatch(pi.GetIndexParameters(), indexes))
{
return pi;
}
}
return null;
}

public override object ChangeType(
object value,
Type myChangeType,
CultureInfo culture)
{
try
{
object newType;
newType = Convert.ChangeType(value, myChangeType);
return newType;
}
// Throw an InvalidCastException if the conversion cannot
// be done by the Convert.ChangeType method.
catch (InvalidCastException)
{
return null;
}
}

public override void ReorderArgumentArray(ref object[] args,
object state)
{
// No operation is needed here because BindToMethod does not
// reorder the args array. The most common implementation
// of this method is shown below.

// ((BinderState)state).args.CopyTo(args, 0);
}

// Returns true only if the type of each object in a matches
// the type of each corresponding object in b.
private bool ParametersMatch(ParameterInfo[] a, object[] b)
{
if (a.Length != b.Length)
{
return false;
}
for (int i = 0; i < a.Length; i++)
{
if (a[i].ParameterType != b[i].GetType())
{
return false;
}
}
return true;
}

// Returns true only if the type of each object in a matches
// the type of each corresponding entry in b.
private bool ParametersMatch(ParameterInfo[] a, Type[] b)
{
if (a.Length != b.Length)
{
return false;
}
for (int i = 0; i < a.Length; i++)
{
if (a[i].ParameterType != b[i])
{
return false;
}
}
return true;
}
}
}

InvokeMember 和 CreateInstance

使用 Type.InvokeMember 调用类型的成员。InvokeMember 可以创建指定类型的新实例,而各种类的 CreateInstance 方法(例如 Activator.CreateInstanceAssembly.CreateInstance)是 InvokeMember 的专用形式。 Binder 类可在这些方法中用于重载解析和参数强制转换。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class CustomBinderDriver
{
public static void Main()
{
Type t = typeof(CustomBinderDriver);
CustomBinder binder = new CustomBinder();
BindingFlags flags = BindingFlags.InvokeMethod | BindingFlags.Instance |
BindingFlags.Public | BindingFlags.Static;
object[] args;

// Case 1. Neither argument coercion nor member selection is needed.
args = new object[] {};
t.InvokeMember("PrintBob", flags, binder, null, args);

// Case 2. Only member selection is needed.
args = new object[] {42};
t.InvokeMember("PrintValue", flags, binder, null, args);

// Case 3. Only argument coercion is needed.
args = new object[] {"5.5"};
t.InvokeMember("PrintNumber", flags, binder, null, args);
}

public static void PrintBob()
{
Console.WriteLine("PrintBob");
}

public static void PrintValue(long value)
{
Console.WriteLine("PrintValue({0})", value);
}

public static void PrintValue(string value)
{
Console.WriteLine("PrintValue\"{0}\")", value);
}

public static void PrintNumber(double value)
{
Console.WriteLine("PrintNumber ({0})", value);
}
}

将程序集加载到仅反射上下文中

参考:如何:将程序集加载到仅反射上下文中

通过仅反射加载上下文,可检查为其他平台或 .NET Framework 的其他版本编译的程序集。 只能检查,不能执行加载到此上下文中的代码。 这意味着无法创建对象,因为无法执行构造函数。 因为该代码无法执行,所以不会自动加载依赖项。 如果该程序集具有依赖项,ReflectionOnlyLoad 方法不会加载这些依赖项。如果需要对依赖项进行检查,必须自行加载。使用 CustomAttributeData.GetCustomAttributes 方法的适当重载获取表示应用于程序集、成员、模块或参数的特性的 CustomAttributeData 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Collections.ObjectModel;

// The example attribute is applied to the assembly.
[assembly:Example(ExampleKind.ThirdKind, Note="This is a note on the assembly.")]

// An enumeration used by the ExampleAttribute class.
public enum ExampleKind
{
FirstKind,
SecondKind,
ThirdKind,
FourthKind
};

// An example attribute. The attribute can be applied to all
// targets, from assemblies to parameters.
//
[AttributeUsage(AttributeTargets.All)]
public class ExampleAttribute : Attribute
{
// Data for properties.
private ExampleKind kindValue;
private string noteValue;
private string[] arrayStrings;
private int[] arrayNumbers;

// Constructors. The parameterless constructor (.ctor) calls
// the constructor that specifies ExampleKind and an array of
// strings, and supplies the default values.
//
public ExampleAttribute(ExampleKind initKind, string[] initStrings)
{
kindValue = initKind;
arrayStrings = initStrings;
}
public ExampleAttribute(ExampleKind initKind) : this(initKind, null) {}
public ExampleAttribute() : this(ExampleKind.FirstKind, null) {}

// Properties. The Note and Numbers properties must be read/write, so they
// can be used as named parameters.
//
public ExampleKind Kind { get { return kindValue; }}
public string[] Strings { get { return arrayStrings; }}
public string Note
{
get { return noteValue; }
set { noteValue = value; }
}
public int[] Numbers
{
get { return arrayNumbers; }
set { arrayNumbers = value; }
}
}

// The example attribute is applied to the test class.
//
[Example(ExampleKind.SecondKind,
new string[] { "String array argument, line 1",
"String array argument, line 2",
"String array argument, line 3" },
Note="This is a note on the class.",
Numbers = new int[] { 53, 57, 59 })]
public class Test
{
// The example attribute is applied to a method, using the
// parameterless constructor and supplying a named argument.
// The attribute is also applied to the method parameter.
//
[Example(Note="This is a note on a method.")]
public void TestMethod([Example] object arg) { }

// Main() gets objects representing the assembly, the test
// type, the test method, and the method parameter. Custom
// attribute data is displayed for each of these.
//
public static void Main()
{
Assembly asm = Assembly.ReflectionOnlyLoad("Source");
Type t = asm.GetType("Test");
MethodInfo m = t.GetMethod("TestMethod");
ParameterInfo[] p = m.GetParameters();

Console.WriteLine("\r\nAttributes for assembly: '{0}'", asm);
ShowAttributeData(CustomAttributeData.GetCustomAttributes(asm));
Console.WriteLine("\r\nAttributes for type: '{0}'", t);
ShowAttributeData(CustomAttributeData.GetCustomAttributes(t));
Console.WriteLine("\r\nAttributes for member: '{0}'", m);
ShowAttributeData(CustomAttributeData.GetCustomAttributes(m));
Console.WriteLine("\r\nAttributes for parameter: '{0}'", p);
ShowAttributeData(CustomAttributeData.GetCustomAttributes(p[0]));
}

private static void ShowAttributeData(
IList<CustomAttributeData> attributes)
{
foreach( CustomAttributeData cad in attributes )
{
Console.WriteLine(" {0}", cad);
Console.WriteLine(" Constructor: '{0}'", cad.Constructor);

Console.WriteLine(" Constructor arguments:");
foreach( CustomAttributeTypedArgument cata
in cad.ConstructorArguments )
{
ShowValueOrArray(cata);
}

Console.WriteLine(" Named arguments:");
foreach( CustomAttributeNamedArgument cana
in cad.NamedArguments )
{
Console.WriteLine(" MemberInfo: '{0}'",
cana.MemberInfo);
ShowValueOrArray(cana.TypedValue);
}
}
}

private static void ShowValueOrArray(CustomAttributeTypedArgument cata)
{
if (cata.Value.GetType() == typeof(ReadOnlyCollection<CustomAttributeTypedArgument>))
{
Console.WriteLine(" Array of '{0}':", cata.ArgumentType);

foreach (CustomAttributeTypedArgument cataElement in
(ReadOnlyCollection<CustomAttributeTypedArgument>) cata.Value)
{
Console.WriteLine(" Type: '{0}' Value: '{1}'",
cataElement.ArgumentType, cataElement.Value);
}
}
else
{
Console.WriteLine(" Type: '{0}' Value: '{1}'",
cata.ArgumentType, cata.Value);
}
}
}

/* This code example produces output similar to the following:

Attributes for assembly: 'source, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
[System.Runtime.CompilerServices.CompilationRelaxationsAttribute((Int32)8)]
Constructor: 'Void .ctor(Int32)'
Constructor arguments:
Type: 'System.Int32' Value: '8'
Named arguments:
[System.Runtime.CompilerServices.RuntimeCompatibilityAttribute(WrapNonExceptionThrows = True)]
Constructor: 'Void .ctor()'
Constructor arguments:
Named arguments:
MemberInfo: 'Boolean WrapNonExceptionThrows'
Type: 'System.Boolean' Value: 'True'
[ExampleAttribute((ExampleKind)2, Note = "This is a note on the assembly.")]
Constructor: 'Void .ctor(ExampleKind)'
Constructor arguments:
Type: 'ExampleKind' Value: '2'
Named arguments:
MemberInfo: 'System.String Note'
Type: 'System.String' Value: 'This is a note on the assembly.'

Attributes for type: 'Test'
[ExampleAttribute((ExampleKind)1, new String[3] { "String array argument, line 1", "String array argument, line 2", "String array argument, line 3" }, Note = "This is a note on the class.", Numbers = new Int32[3] { 53, 57, 59 })]
Constructor: 'Void .ctor(ExampleKind, System.String[])'
Constructor arguments:
Type: 'ExampleKind' Value: '1'
Array of 'System.String[]':
Type: 'System.String' Value: 'String array argument, line 1'
Type: 'System.String' Value: 'String array argument, line 2'
Type: 'System.String' Value: 'String array argument, line 3'
Named arguments:
MemberInfo: 'System.String Note'
Type: 'System.String' Value: 'This is a note on the class.'
MemberInfo: 'Int32[] Numbers'
Array of 'System.Int32[]':
Type: 'System.Int32' Value: '53'
Type: 'System.Int32' Value: '57'
Type: 'System.Int32' Value: '59'

Attributes for member: 'Void TestMethod(System.Object)'
[ExampleAttribute(Note = "This is a note on a method.")]
Constructor: 'Void .ctor()'
Constructor arguments:
Named arguments:
MemberInfo: 'System.String Note'
Type: 'System.String' Value: 'This is a note on a method.'

Attributes for parameter: 'System.Object arg'
[ExampleAttribute()]
Constructor: 'Void .ctor()'
Constructor arguments:
Named arguments:
*/

CSharp基础-认识反射-上

本章包括查看对象类型信息,反射类型和泛型反射,反射检查和实例化泛型类型。

反射概述

在程序中,当我们需要动态的去加载程序集的时候(将对程序集的引用由编译时推移到运行时),反射是一种很好的选择。可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问其字段和属性。反射为.NET类型提供了高度的动态能力,包括:元数据的动态查询、绑定与执行、动态代码生成。常用的反射类型包含在System.ReflectionSystem.Reflection.Emit ,反射包括程序集反射、类型反射、接口反射、类型成员反射。

反射在以下情况下很有用:

  • 需要访问程序元数据中的特性时。
  • 检查和实例化程序集中的类型。
  • 在运行时构建新类型。 使用 System.Reflection.Emit 中的类。
  • 执行后期绑定,访问在运行时创建的类型上的方法。 请参阅主题 Dynamically Loading and Using Types(动态加载和使用类型)。

运行时类型标识(RTTI)

运行时类型信息 (RTTI,Run-Time Type Information) 是一种允许在程序执行过程中确定对象类型的机制。例如使用它能够确切地知道基类引用指向了什么类型对象。运行时类型标识,能预先测试某个强制类型转换操作,能否成功,从而避免无效的强制类型转换异常。

在c#中有三个支持RTTI的关键字:isastypeof

is 运算符

参考:is(C# 参考)

能够判断对象类型是否为特定类型,如果两种类型是相同类型,或者两者之间存在引用,装箱拆箱转换,则表明两种类型是兼容的。或(从 C# 7.0 开始)针对某个模式测试表达式。

类型兼容性测试

语法:

1
2
3
4
5
6
7
8
9
expr is type

if (obj is Person) {
// Do something if obj is a Person.
}

double number1 = 12.63;
if (Math.Ceiling(number1) is double)
Console.WriteLine("The expression returns a double.");

expr 是计算结果为某个类型的实例的表达式,而 type 是 expr 结果要转换到的类型的名称。
expr 可以是返回值的任何表达式,匿名方法和 Lambda 表达式除外。

如果满足以下条件,则 is 语句为 true:

  • expr 是与 type 具有相同类型的一个实例。
  • expr 是派生自 type 的类型的一个实例。 换言之,expr 结果可以向上转换为 type 的一个实例。
  • expr 具有属于 type 的一个基类的编译时类型,expr 还具有属于 type 或派生自 type 的运行时类型。 变量的编译时类型是其声明中定义的变量类型。 变量的运行时类型是分配给该变量的实例类型。
  • expr 是实现 type 接口的类型的一个实例。

利用 is 的模式匹配

从 C# 7.0 开始,isswitch 语句支持模式匹配。

** 类型模式**

用于测试表达式是否可转换为指定类型,如果可以,则将其转换为该类型的一个变量。

语法:

1
expr is type varname

如果 expr 为 true,且 isif 语句一起使用,则会分配 varname,并且其仅在 if 语句中具有局部范围。

1
2
3
4
if (o is Employee e)
{
return Name.CompareTo(e.Name);
}

** 常量模式**

使用常量模式执行模式匹配时,is 会测试表达式结果是否等于指定常量。

1
expr is constant

其中 expr 是要计算的表达式,constant 是要测试的值。 constant 可以是以下任何常数表达式:

  • 一个文本值。
  • 已声明 const 变量的名称。
  • 一个枚举常量。

常数表达式的计算方式如下:

  • 如果 expr 和 constant 均为整型类型,则 C# 相等运算符确定表示式是否返回 true(即,是否为 expr == constant)。
  • 否则,由对静态 Object.Equals(expr, constant) 方法的调用来确定表达式的值。

is 语句支持 null 关键字。

1
2
3
4
expr is null

if (o is null)
{}

** var 模式**

具有 var 模式的模式匹配始终成功。 语法为

1
expr is var varname
1
2
3
4
foreach (var item in items) {
if (item is var obj)
Console.WriteLine($"Type: {obj.GetType().Name}, Value: {obj}");
}

请注意,如果 expr 为 null,则 is 表达式仍为 true 并向 varname 分配 null。

as 运算符

参考:as(C# 参考)

在运行期间执行类型转换,并且能够使得类型转换失败不抛异常,而返回一个null值。

1
expression as type  
1
2
3
4
5
Base b = d as Base;
if (b != null)
{
Console.WriteLine(b.ToString());
}

typeof运算符

参考:typeof(C# 参考)

获取类型的System.Type 对象。

1
System.Type type = typeof(int);

获取表达式的运行时类型:

1
2
int i = 0;
System.Type type = i.GetType();

示例显示如何确定方法的返回类型是否为泛型 IEnumerable<T>。 如果返回类型不是 IEnumerable<T> 泛型类型,则 Type.GetInterface 将返回 null

1
2
3
4
5
6
7
8
MethodInfo method = typeof(string).GetMethod("Copy");
Type t = method.ReturnType.GetInterface(typeof(IEnumerable<>).Name);
if (t != null)
Console.WriteLine(t);
else
Console.WriteLine("The return type is not IEnumerable<T>.");

Console.ReadKey();

查看类型信息

参考:查看类型信息

模块和程序集

模块是程序集中代码的逻辑集合。您可以在程序集内部拥有多个模块,并且每个模块都可以使用不同的.NET语言编写(VS不支持创建多模块程序集)。

程序集包含模块。模块包含类。类包含函数。

System.Type 类是反射的中心。 当反射提出请求时,公共语言运行时为已加载的类型创建 Type。 可使用 Type 对象的方法、字段、属性和嵌套类来查找该类型的任何信息。

Type 类派生于 System.Reflection.MemberInfo 抽象类。

获取程序集的 Assembly 对象和模块:

1
Assembly a = typeof(object).Module.Assembly;

Type.GetTypes()

从已加载的程序集获取 Type 对象:

1
2
3
4
5
6
7
8
// Loads an assembly using its file name.
Assembly a = Assembly.LoadFrom("MyExe.exe");
// Gets the type names from the assembly.
Type[] types2 = a.GetTypes();
foreach (Type t in types2)
{
Console.WriteLine(t.FullName);
}

使用 Type.Module 属性获取一个封装含该类型的模块的对象。 使用 Module.Assembly 属性查找一个封装含该模块的程序集的对象。 可以获取直接使用 Type.Assembly 属性封装类型的程序集。

Type.GetMembers()

至少要有Instance(或Static)与Public(或NonPublic)标记。

1
2
Type.GetMembers() // 获取所有公共成员。不包括 private 和 protected 访问权限
Type.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static) // 包含非公开;包含实例成员;包含公开;只包含本类声明的成员,不考虑继承的成员;包含静态成员。
1
2
3
4
Type.GetFields() // 获取字段
Type.GetProperties() // 获取属性
Type.GetEvents() // 获取事件
Type.GetMethods() // 获取方法

Type.ConstructorInfo()

列出类的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static void Main(string[] args)
{
Type t = typeof(System.String);
Console.WriteLine("Listing all the public constructors of the {0} type", t);
// Constructors.
ConstructorInfo[] ci = t.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
Console.WriteLine("//Constructors");
PrintMembers(ci);

Console.ReadKey();
}

public static void PrintMembers(MemberInfo[] ms)
{
foreach (MemberInfo m in ms)
{
Console.WriteLine("{0}{1}", " ", m);
}
Console.WriteLine();
}

反射类型和泛型类型

从反射的角度来说,泛型类型和普通类型之间的区别在于泛型类型具有与之关联的一组类型形参(若是泛型类型定义)或类型实参(若是构造类型)。

泛型类型和泛型方法

检查未知类型(由 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 方法来获取泛型方法定义。

例如,如果你有一个表示 TypeDictionary<int, string> 对象且想创建类型 Dictionary<string, MyClass>,则可以使用 GetGenericTypeDefinition 方法来获取表示 TypeDictionary<TKey, TValue> ,然后使用 MakeGenericType 方法来生成表示 TypeDictionary<int, MyClass>

检查类型实参和类型形参

Type.GetGenericArguments 方法来获取 Type 对象的数组(表示泛型类型的类型形参或类型实参); MethodInfo.GetGenericArguments 方法为泛型方法获取 MethodInfo(表示泛型类型的类型形参或类型实参)。如果元素是类型形参,则 IsGenericParameter 属性为 true

如何使用反射

参考:如何:使用反射检查和实例化泛型类型

获取泛型类型信息的方式与获取其他类型信息的方式相同:检查表示泛型类型的 Type 对象。 主要的差异在于,泛型类型具有表示其泛型类型参数的 Type 对象列表。

检查泛型类型和类型参数

范例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
private static void Main(string[] args)
{
// 1,获取表示泛型类型的 Type 实例
Type t = typeof(Dictionary<,>);

// 2, 使用 IsGenericType 属性确定类型是否为泛型,
// 然后使用 IsGenericTypeDefinition 属性确定类型是否为泛型类型定义。
Console.WriteLine(" Is this a generic type? {0}",
t.IsGenericType);
Console.WriteLine(" Is this a generic type definition? {0}",
t.IsGenericTypeDefinition);

// 3,使用 GetGenericArguments 方法获取包含泛型类型参数的数组。
Type[] typeParameters = t.GetGenericArguments();

// 4,使用 IsGenericParameter 属性确定它是类型形参(例如,在泛型类型定义中),
// 还是为类型形参指定的类型(例如,在构造类型中)
Console.WriteLine(" List {0} type arguments:",
typeParameters.Length);
foreach (Type tParam in typeParameters)
{
if (tParam.IsGenericParameter)
{
DisplayGenericParameter(tParam);
}
else
{
Console.WriteLine(" Type argument: {0}",
tParam);
}
}
Console.ReadKey();
}

private static void DisplayGenericParameter(Type tp)
{
// 5,表示泛型类型参数的 Type 对象的名称和参数位置。
Console.WriteLine(" Type parameter: {0} position {1}",
tp.Name, tp.GenericParameterPosition);

Type classConstraint = null;

// 6,GetGenericParameterConstraints 方法获取单个数组中的所有约束,
// 确定泛型类型参数的基类型约束和接口约束。无序
foreach (Type iConstraint in tp.GetGenericParameterConstraints())
{
if (iConstraint.IsInterface)
{
Console.WriteLine(" Interface constraint: {0}",
iConstraint);
}
}

if (classConstraint != null)
{
Console.WriteLine(" Base type constraint: {0}",
tp.BaseType);
}
else
Console.WriteLine(" Base type constraint: None");

// 7, GenericParameterAttributes 属性发现类型参数的特殊约束
GenericParameterAttributes sConstraints =
tp.GenericParameterAttributes &
GenericParameterAttributes.SpecialConstraintMask;

if (sConstraints == GenericParameterAttributes.None)
{
Console.WriteLine(" No special constraints.");
}
else
{
if (GenericParameterAttributes.None != (sConstraints &
GenericParameterAttributes.DefaultConstructorConstraint))
{
Console.WriteLine(" Must have a parameterless constructor.");
}
if (GenericParameterAttributes.None != (sConstraints &
GenericParameterAttributes.ReferenceTypeConstraint))
{
Console.WriteLine(" Must be a reference type.");
}
if (GenericParameterAttributes.None != (sConstraints &
GenericParameterAttributes.NotNullableValueTypeConstraint))
{
Console.WriteLine(" Must be a non-nullable value type.");
}
}
}

构造泛型类型的实例

需要指定其泛型类型参数的实际类型,否则不能创建泛型类型的实例。使用 MakeGenericType方法。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Security.Permissions;

namespace ConsoleApp1
{
// Define an example interface.
public interface ITestArgument { }

// Define an example base class.
public class TestBase { }

// Define a generic class with one parameter. The parameter
// has three constraints: It must inherit TestBase, it must
// implement ITestArgument, and it must have a parameterless
// constructor.
public class Test<T> where T : TestBase, ITestArgument, new() { }

// Define a class that meets the constraints on the type
// parameter of class Test.
public class TestArgument : TestBase, ITestArgument
{
public TestArgument()
{
}
}

public class Example
{
// The following method displays information about a generic
// type.
private static void DisplayGenericType(Type t)
{
Console.WriteLine("\r\n {0}", t);
Console.WriteLine(" Is this a generic type? {0}",
t.IsGenericType);
Console.WriteLine(" Is this a generic type definition? {0}",
t.IsGenericTypeDefinition);

// Get the generic type parameters or type arguments.
Type[] typeParameters = t.GetGenericArguments();

Console.WriteLine(" List {0} type arguments:",
typeParameters.Length);
foreach (Type tParam in typeParameters)
{
if (tParam.IsGenericParameter)
{
DisplayGenericParameter(tParam);
}
else
{
Console.WriteLine(" Type argument: {0}",
tParam);
}
}
}

// The following method displays information about a generic
// type parameter. Generic type parameters are represented by
// instances of System.Type, just like ordinary types.
private static void DisplayGenericParameter(Type tp)
{
Console.WriteLine(" Type parameter: {0} position {1}",
tp.Name, tp.GenericParameterPosition);

Type classConstraint = null;

foreach (Type iConstraint in tp.GetGenericParameterConstraints())
{
if (iConstraint.IsInterface)
{
Console.WriteLine(" Interface constraint: {0}",
iConstraint);
}
}

if (classConstraint != null)
{
Console.WriteLine(" Base type constraint: {0}",
tp.BaseType);
}
else
Console.WriteLine(" Base type constraint: None");

GenericParameterAttributes sConstraints =
tp.GenericParameterAttributes &
GenericParameterAttributes.SpecialConstraintMask;

if (sConstraints == GenericParameterAttributes.None)
{
Console.WriteLine(" No special constraints.");
}
else
{
if (GenericParameterAttributes.None != (sConstraints &
GenericParameterAttributes.DefaultConstructorConstraint))
{
Console.WriteLine(" Must have a parameterless constructor.");
}
if (GenericParameterAttributes.None != (sConstraints &
GenericParameterAttributes.ReferenceTypeConstraint))
{
Console.WriteLine(" Must be a reference type.");
}
if (GenericParameterAttributes.None != (sConstraints &
GenericParameterAttributes.NotNullableValueTypeConstraint))
{
Console.WriteLine(" Must be a non-nullable value type.");
}
}
}

[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public static void Main()
{
// Two ways to get a Type object that represents the generic
// type definition of the Dictionary class.
//
// Use the typeof operator to create the generic type
// definition directly. To specify the generic type definition,
// omit the type arguments but retain the comma that separates
// them.
Type d1 = typeof(Dictionary<,>);

// You can also obtain the generic type definition from a
// constructed class. In this case, the constructed class
// is a dictionary of Example objects, with String keys.
Dictionary<string, Example> d2 = new Dictionary<string, Example>();
// Get a Type object that represents the constructed type,
// and from that get the generic type definition. The
// variables d1 and d4 contain the same type.
Type d3 = d2.GetType();
Type d4 = d3.GetGenericTypeDefinition();

// Display information for the generic type definition, and
// for the constructed type Dictionary<String, Example>.
DisplayGenericType(d1);
DisplayGenericType(d2.GetType());

// Construct an array of type arguments to substitute for
// the type parameters of the generic Dictionary class.
// The array must contain the correct number of types, in
// the same order that they appear in the type parameter
// list of Dictionary. The key (first type parameter)
// is of type string, and the type to be contained in the
// dictionary is Example.
Type[] typeArgs = { typeof(string), typeof(Example) };

// Construct the type Dictionary<String, Example>.
Type constructed = d1.MakeGenericType(typeArgs);

DisplayGenericType(constructed);

object o = Activator.CreateInstance(constructed);

Console.WriteLine("\r\nCompare types obtained by different methods:");
Console.WriteLine(" Are the constructed types equal? {0}",
(d2.GetType() == constructed));
Console.WriteLine(" Are the generic definitions equal? {0}",
(d1 == constructed.GetGenericTypeDefinition()));

// Demonstrate the DisplayGenericType and
// DisplayGenericParameter methods with the Test class
// defined above. This shows base, interface, and special
// constraints.
DisplayGenericType(typeof(Test<>));

Console.ReadKey();
}
}
}

参考:

反射 (C#)

.NET Framework 中的反射

C#之玩转反射

C#反射用法,入门到精通

SOLID原则

设计类和模块时,遵守 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)

单一职责原则(SRP)表明一个类有且只有一个职责。

开放封闭原则(OCP)

开放封闭原则(OCP)指出,一个类应该对扩展开放,对修改关闭。这意味一旦你创建了一个类并且应用程序的其他部分开始使用它,你不应该修改它。即,可扩展(extension),不可修改(modification)。

里氏替换原则(LSP)

里氏替换原则指出,派生的子类应该是可替换基类的,也就是说任何基类可以出现的地方,子类一定可以出现。一个对象在其出现的任何地方,都可以用子类实例做替换,并且不会导致程序的错误。换句话说,当子类可以在任意地方替换基类且软件功能不受影响时,这种继承关系的建模才是合理的。当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系。

接口隔离原则(ISP)

接口隔离原则(ISP)表明类不应该被迫依赖他们不使用的方法,也就是说一个接口应该拥有尽可能少的行为。将接口拆分成更小和更具体的接口,有助于解耦,从而更容易重构、更改。

依赖倒置原则(DIP)

依赖倒置原则(DIP)表明高层模块不应该依赖低层模块,相反,他们应该依赖抽象类或者接口。这个设计原则的亮点在于任何被DI框架注入的类很容易用mock对象进行测试和维护,因为对象创建代码集中在框架中,客户端代码也不混乱。

参考:

浅谈 SOLID 原则的具体使用

SOLID原则

IQueryable和IQueryProvider接口

IQueryable<T> 接口是 Linq to Provider的入口。

IQueryable接口

在.NET中,IQueryable<T> 继承于 IEnumerable<T> , IEnumerableIQueryable 接口

1
2
3
4
5
6
7
8
9
10
11
12
public interface IQueryable<out T> : IEnumerable<T>, IEnumerable, IQueryable
{
}

public interface IQueryable : IEnumerable
{
Expression Expression { [__DynamicallyInvokable] get; }

Type ElementType { [__DynamicallyInvokable] get; }

IQueryProvider Provider { [__DynamicallyInvokable] get; }
}
  • ElementType :当前Query所对应的类型
  • Expression :获取与 IQueryable 的实例关联的表达式树
  • Provider:获取与此数据源关联的查询提供程序

我们所有定义在查询表达式中方法调用或者Lambda表达式都将由该Expression属性表示,最终会由Provider表示的提供程序翻译为它所对应的数据源的查询语言。

IQueryable 扩展方法传入的是 Expression 类型

IEnumerable接口

IEnumrable 扩展方法传入的是委托。

1
2
3
4
5
6
7
8
9
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}

public interface IEnumerable
{
IEnumerator GetEnumerator();
}

IQueryProvider接口

1
2
3
4
5
6
7
8
9
10
public interface IQueryProvider
{
IQueryable CreateQuery(Expression expression);

IQueryable<TElement> CreateQuery<TElement>(Expression expression);

object Execute(Expression expression);

TResult Execute<TResult>(Expression expression);
}

Provider 负责执行表达式目录树并返回结果。如果是 LINQ to SQLProvider ,则它会负责把表达式目录树翻译为T-SQL语句并并传递给数据库服务器,并返回最后的执行的结果;如果是一个 Web ServiceProvider,则它会负责翻译表达式目录树并调用 Web Service,最终返回结果。

自定义Provider

首先,实现一个 Query<T> 类,它实现 IQueryable<T> 接口,提供了一个 IQueryProviderExpression 属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class Query<T> : IQueryable<T>, IQueryable, IEnumerable<T>, IEnumerable, IOrderedQueryable<T>, IOrderedQueryable
{
private readonly QueryProvider provider;

private readonly Expression expression;

public Query(QueryProvider provider)
{
this.provider = provider ?? throw new ArgumentNullException(nameof(provider));

this.expression = Expression.Constant(this);
}

public Query(QueryProvider provider, Expression expression)
{
if (expression == null)
{
throw new ArgumentNullException(nameof(expression));
}

if (!typeof(IQueryable<T>).IsAssignableFrom(expression.Type))
{
throw new ArgumentOutOfRangeException(nameof(expression));
}

this.provider = provider ?? throw new ArgumentNullException(nameof(provider));

this.expression = expression;
}

Expression IQueryable.Expression => this.expression;

Type IQueryable.ElementType => typeof(T);

IQueryProvider IQueryable.Provider => this.provider;

public IEnumerator<T> GetEnumerator()
{
return ((IEnumerable<T>)this.provider.Execute(this.expression)).GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)this.provider.Execute(this.expression)).GetEnumerator();
}

public override string ToString()
{
return this.provider.GetQueryText(this.expression);
}
}

然后,定义一个抽象类 QueryProvider,它将会实现 IQueryProvider 接口,包括 CreateQueryExecute方法(都包含泛型方法)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public abstract class QueryProvider : IQueryProvider
{
protected QueryProvider()
{
}

IQueryable<S> IQueryProvider.CreateQuery<S>(Expression expression)
{
return new Query<S>(this, expression);
}

IQueryable IQueryProvider.CreateQuery(Expression expression)
{
Type elementType = TypeSystem.GetElementType(expression.Type);

try
{
return (IQueryable)Activator.CreateInstance(typeof(Query<>).MakeGenericType(elementType), new object[] { this, expression });
}
catch (TargetInvocationException tie)
{
throw tie.InnerException;
}
}

S IQueryProvider.Execute<S>(Expression expression)
{
return (S)this.Execute(expression);
}

object IQueryProvider.Execute(Expression expression)
{
return this.Execute(expression);
}

public abstract string GetQueryText(Expression expression);

public abstract object Execute(Expression expression);
}

最后定义一个具体 Provider,继承自抽象类 QueryProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CnblogsQueryProvider : QueryProvider
{
public override String GetQueryText(Expression expression)
{
SearchCriteria criteria;

// 翻译查询条件
criteria = new PostExpressionVisitor().ProcessExpression(expression);

// 生成URL
String url = PostHelper.BuildUrl(criteria);

return url;
}

public override object Execute(Expression expression)
{
String url = GetQueryText(expression);
IEnumerable<Post> results = PostHelper.PerformWebQuery(url);
return results;
}
}

调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void Main(string[] args)
{
var provider = new CnblogsQueryProvider();
var queryable = new Query<Post>(provider);

var query =
from p in queryable
where p.Diggs >= 10 &&
p.Comments > 10 &&
p.Views > 10 &&
p.Comments < 20
select p;

var list = query.ToList();


Console.ReadLine();
}

源码:https://github.com/syxdevcode/LinqToCnBlogs

参考:

打造自己的LINQ Provider(中):IQueryable和IQueryProvider

由浅入深表达式树(完结篇)重磅打造 Linq To 博客园

打造自己的LINQ Provider(中):IQueryable和IQueryProvider

LINQ: Building an IQueryable Provider – Part I

遍历/修改表达式树

可以使用 ExpressionVisitor 类遍历现有表达式树,以及复制它访问的每个节点。

表达式的遍历

参考:

ExpressionType

Expression

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.AddAssign
ExpressionType.AddAssignChecked
ExpressionType.AddChecked
ExpressionType.And
ExpressionType.AndAlso
ExpressionType.AndAssign
ExpressionType.ArrayIndex
ExpressionType.Assign
ExpressionType.Coalesce
ExpressionType.Divide
ExpressionType.DivideAssign
ExpressionType.Equal
ExpressionType.GreaterThan
ExpressionType.GreaterThanOrEqual
ExpressionType.LessThan
ExpressionType.LessThanOrEqual
ExpressionType.Modulo
ExpressionType.ModuloAssign
ExpressionType.Multiply
ExpressionType.MultiplyAssign
ExpressionType.NotEqual
ExpressionType.Or
ExpressionType.OrElse
ExpressionType.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.NewArrayBounds
ExpressionType.NewArrayInit
VisitNewArray(NewArrayExpression)
InvocationExpression ExpressionType.Invoke VisitInvocation(InvocationExpression)
ListInitExpression ExpressionType.ListInit VisitListInit(ListInitExpression)
TypeBinaryExpression ExpressionType.TypeIs VisitTypeBinary(TypeBinaryExpression)

扩展IQueryable的Where方法,根据输入的查询条件来构造SQL语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
internal class Program
{
private static void Main(string[] args)
{
List<User> myUsers = new List<User>();
var userSql = myUsers.AsQueryable().Where(u => u.Age > 2);
Console.WriteLine(userSql);
// SELECT * FROM (SELECT * FROM User) AS T WHERE (Age>2)

List<User> myUsers2 = new List<User>();
var userSql2 = myUsers2.AsQueryable().Where(u => u.Name == "QueryExtension");
Console.WriteLine(userSql2);
//SELECT * FROM (SELECT * FROM USER) AS T WHERE (Name='QueryExtension')

Console.ReadKey();
}
}

public class User
{
public string Name { get; set; }
public int Age { get; set; }
}

public static class QueryExtensions
{
public static string Where<TSource>(this IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate)
{
var expression = Expression.Call(null, ((MethodInfo)MethodBase.GetCurrentMethod())
.MakeGenericMethod(new Type[] { typeof(TSource) }),
new Expression[] { source.Expression, Expression.Quote(predicate) });

var translator = new QueryTranslator();
return translator.Translate(expression);
}
}

internal class QueryTranslator : ExpressionVisitor
{
private StringBuilder sb;

internal string Translate(Expression expression)
{
this.sb = new StringBuilder();
this.Visit(expression);
return this.sb.ToString();
}

private static Expression StripQuotes(Expression e)
{
while (e.NodeType == ExpressionType.Quote)
{
e = ((UnaryExpression)e).Operand;
}
return e;
}

protected override Expression VisitMethodCall(MethodCallExpression m)
{
if (m.Method.DeclaringType == typeof(QueryExtensions) && m.Method.Name == "Where")
{
sb.Append("SELECT * FROM (");
this.Visit(m.Arguments[0]);
sb.Append(") AS T WHERE ");
LambdaExpression lambda = (LambdaExpression)StripQuotes(m.Arguments[1]);
this.Visit(lambda.Body);
return m;
}
throw new NotSupportedException(string.Format("方法{0}不支持", m.Method.Name));
}

protected override Expression VisitUnary(UnaryExpression u)
{
switch (u.NodeType)
{
case ExpressionType.Not:
sb.Append(" NOT ");
this.Visit(u.Operand);
break;

default:
throw new NotSupportedException(string.Format("运算{0}不支持", u.NodeType));
}
return u;
}

protected override Expression VisitBinary(BinaryExpression b)
{
sb.Append("(");
this.Visit(b.Left);
switch (b.NodeType)
{
case ExpressionType.And:
sb.Append(" AND ");
break;

case ExpressionType.Or:
sb.Append(" OR");
break;

case ExpressionType.Equal:
sb.Append(" = ");
break;

case ExpressionType.NotEqual:
sb.Append(" <> ");
break;

case ExpressionType.LessThan:
sb.Append(" < ");
break;

case ExpressionType.LessThanOrEqual:
sb.Append(" <= ");
break;

case ExpressionType.GreaterThan:
sb.Append(" > ");
break;

case ExpressionType.GreaterThanOrEqual:
sb.Append(" >= ");
break;

default:
throw new NotSupportedException(string.Format("运算符{0}不支持", b.NodeType));
}
this.Visit(b.Right);
sb.Append(")");
return b;
}

protected override Expression VisitConstant(ConstantExpression c)
{
IQueryable q = c.Value as IQueryable;
if (q != null)
{
// 我们假设我们那个Queryable就是对应的表
sb.Append("SELECT * FROM ");
sb.Append(q.ElementType.Name);
}
else if (c.Value == null)
{
sb.Append("NULL");
}
else
{
switch (Type.GetTypeCode(c.Value.GetType()))
{
case TypeCode.Boolean:
sb.Append(((bool)c.Value) ? 1 : 0);
break;

case TypeCode.String:
sb.Append("'");
sb.Append(c.Value);
sb.Append("'");
break;

case TypeCode.Object:
throw new NotSupportedException(string.Format("常量{0}不支持", c.Value));
default:
sb.Append(c.Value);
break;
}
}
return c;
}

protected override Expression VisitMember(MemberExpression m)
{
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter)
{
sb.Append(m.Member.Name);
return m;
}
throw new NotSupportedException(string.Format("成员{0}不支持", m.Member.Name));
}
}

修改表达式树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class OperationsVisitor : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return this.Visit(expression);
}

protected override Expression VisitBinary(BinaryExpression b)
{
if (b.NodeType == ExpressionType.Add)
{
Expression left = this.Visit(b.Left); // // 或b.Left
Expression right = this.Visit(b.Right); // 或b.Right
return Expression.Subtract(left,right);
}

return base.VisitBinary(b);
}
}

static void Main(string[] args)
{
Expression<Func<int, int, int>> lambda = (a, b) => a + b * 2;

var operationsVisitor = new OperationsVisitor();
Expression modifyExpression = operationsVisitor.Modify(lambda);

Console.WriteLine(modifyExpression.ToString());
}

参考:

表达式树

ExpressionVisitor

System.Linq.Expressions

由浅入深表达式树(二)遍历表达式树

CSharp基础-表达式树

表达式树(Expression Tree)是.NET 3.5推出的。
表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。

表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET Framework 之间的互操作性,同时保证编译器编写员能够发射表达式树而非 Microsoft 中间语言 (MSIL)。

Expressions 类

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 块。

参考:System.Linq.Expressions

创建表达式树

Lambda 表达式创建表达式树

若 lambda 表达式被分配给 Expression<TDelegate> 类型的变量,则编译器可以发射代码以创建表示该 lambda 表达式的表达式树。
C# 编译器只能从表达式 Lambda(或单行 Lambda)生成表达式树。 它无法解析语句 lambda (或多行 lambda)。

如:

1
2
3
4
5
6
// 示例展示如何通过 C# 编译器创建表示 Lambda 表达式 num => num < 5 的表达式树
Expression<Func<int, bool>> lambda = num => num < 5;

// 下面的代码编译不通过
Expression<Func<int, int, int>> expr2 = (x, y) => { return x + y; };
Expression<Action<int>> expr3 = x => { };

Expression<TDelegate> 是直接继承自 LambdaExpression

1
2
3
4
5
6
7
public sealed class Expression<TDelegate> : LambdaExpression
{
internal Expression(Expression body, string name, bool tailCall, ReadOnlyCollection<ParameterExpression> parameters) :
base(typeof(TDelegate), name, body, tailCall, parameters)
{
}
}

通过 API 创建表达式树

通过 API 创建表达式树需要使用 Expression 类。类包含创建特定类型表达式树节点的静态工厂方法,比如表示参数变量的 ParameterExpression,或者是表示方法调用的 MethodCallExpressionParameterExpression 名称空间还解释了 MethodCallExpressionSystem.Linq.Expressions和另一种具体表达式类型。 这些类型来源于抽象类型 Expression

使用 API 创建表示 Lambda 表达式 num => num < 5 的表达式树:

1
2
3
4
5
6
7
8
9
10
11
using System.Linq.Expressions;

// Manually build the expression tree for
// the lambda expression num => num < 5.
ParameterExpression numParam = Expression.Parameter(typeof(int), "num");
ConstantExpression five = Expression.Constant(5, typeof(int));
BinaryExpression numLessThanFive = Expression.LessThan(numParam, five);
Expression<Func<int, bool>> lambda1 =
Expression.Lambda<Func<int, bool>>(
numLessThanFive,
new ParameterExpression[] { numParam });

在 .NET Framework 4 或更高版本中,表达式树 API 还支持赋值表达式和控制流表达式,例如循环、条件块和 try-catch 块等。下列示例展示如何创建计算数字阶乘的表达式树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Creating a parameter expression.  
ParameterExpression value = Expression.Parameter(typeof(int), "value");

// Creating an expression to hold a local variable.
ParameterExpression result = Expression.Parameter(typeof(int), "result");

// Creating a label to jump to from a loop.
LabelTarget label = Expression.Label(typeof(int));

// Creating a method body.
BlockExpression block = Expression.Block(
// Adding a local variable.
new[] { result },
// Assigning a constant to a local variable: result = 1
Expression.Assign(result, Expression.Constant(1)),
// Adding a loop.
Expression.Loop(
// Adding a conditional block into the loop.
Expression.IfThenElse(
// Condition: value > 1
Expression.GreaterThan(value, Expression.Constant(1)),
// If true: result *= value --
Expression.MultiplyAssign(result,
Expression.PostDecrementAssign(value)),
// If false, exit the loop and go to the label.
Expression.Break(label, result)
),
// Label to jump to.
label
)
);

// Compile and execute an expression tree.
int factorial = Expression.Lambda<Func<int, int>>(block, value).Compile()(5);

Console.WriteLine(factorial);
// Prints 120.

解析表达式树

下列代码示例展示如何分解表示 Lambda 表达式 num => num < 5 的表达式树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Add the following using directive to your code file:  
// using System.Linq.Expressions;

// Create an expression tree.
Expression<Func<int, bool>> exprTree = num => num < 5;

// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;

Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
param.Name, left.Name, operation.NodeType, right.Value);

// This code produces the following output:

// Decomposed expression: num => num LessThan 5

编译表达式树

Expression<TDelegate> 类型提供了 Compile 方法以将表达式树表示的代码编译成可执行委托。

下列代码示例展示如何编译表达式树并运行结果代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Creating an expression tree.  
Expression<Func<int, bool>> expr = num => num < 5;

// Compiling the expression tree into a delegate.
Func<int, bool> result = expr.Compile();

// Invoking the delegate and writing the result to the console.
Console.WriteLine(result(4));

// Prints True.

// You can also use simplified syntax
// to compile and run an expression tree.
// The following line can replace two previous statements.
Console.WriteLine(expr.Compile()(4));

// Also prints True.

执行表达式树

Lambda 表达式的表达式树的类型为 LambdaExpressionExpression<TDelegate>。 若要执行这些表达式树,调用 Compile 方法来创建一个可执行的委托,然后调用该委托。

备注:

如果委托的类型未知,也就是说 Lambda 表达式的类型为 LambdaExpression,而不是 Expression<TDelegate>,则必须对委托调用 DynamicInvoke 方法,而不是直接调用委托。如 lambdaExpr.Compile().DynamicInvoke(1);

下面的代码示例演示如何通过创建 lambda 表达式并执行它来执行代表幂运算的表达式树。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// The expression tree to execute.  
BinaryExpression be = Expression.Power(Expression.Constant(2D), Expression.Constant(3D));

// Create a lambda expression.
Expression<Func<double>> le = Expression.Lambda<Func<double>>(be);

// Compile the lambda expression.
Func<double> compiledExpression = le.Compile();

// Execute the lambda expression.
double result = compiledExpression();

// Display the result.
Console.WriteLine(result);

// This code produces the following output:
// 8

修改表达式树 (C#)

表达式树是不可变的,这意味着不能直接对它们进行修改。 若要更改表达式树,必须创建现有表达式树的副本,创建此副本后,进行必要的更改。 可以使用 ExpressionVisitor 类遍历现有表达式树,以及复制它访问的每个节点。

运算从条件 AND 更改为条件 OR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private static void Main(string[] args)
{
Expression<Func<string, bool>> expr = name => name.Length > 10 && name.StartsWith("G");
Console.WriteLine(expr);

AndAlsoModifier treeModifier = new AndAlsoModifier();
Expression modifiedExpr = treeModifier.Modify((Expression)expr);

Console.WriteLine(modifiedExpr);

/* This code produces the following output:

name => ((name.Length > 10) && name.StartsWith("G"))
name => ((name.Length > 10) || name.StartsWith("G"))
*/

Console.Read();
}

public class AndAlsoModifier : ExpressionVisitor
{
public Expression Modify(Expression expression)
{
return Visit(expression);
}

protected override Expression VisitBinary(BinaryExpression b)
{
if (b.NodeType == ExpressionType.AndAlso)
{
Expression left = this.Visit(b.Left);
Expression right = this.Visit(b.Right);

// Make this binary expression an OrElse operation instead of an AndAlso operation.
return Expression.MakeBinary(ExpressionType.OrElse, left, right, b.IsLiftedToNull, b.Method);
}

return base.VisitBinary(b);
}
}

使用表达式树来生成动态查询

在 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// Add a using directive for System.Linq.Expressions.
string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
"Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
"Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
"Blue Yonder Airlines", "Trey Research", "The Phone Company",
"Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };

// The IQueryable data to query.
IQueryable<String> queryableData = companies.AsQueryable<string>();

// Compose the expression tree that represents the parameter to the predicate.
ParameterExpression pe = Expression.Parameter(typeof(string), "company");

// ***** Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) *****
// Create an expression tree that represents the expression 'company.ToLower() == "coho winery"'.
Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Expression right = Expression.Constant("coho winery");
Expression e1 = Expression.Equal(left, right);

// Create an expression tree that represents the expression 'company.Length > 16'.
left = Expression.Property(pe, typeof(string).GetProperty("Length"));
right = Expression.Constant(16, typeof(int));
Expression e2 = Expression.GreaterThan(left, right);

// Combine the expression trees to create an expression tree that represents the
// expression '(company.ToLower() == "coho winery" || company.Length > 16)'.
Expression predicateBody = Expression.OrElse(e1, e2);

// Create an expression tree that represents the expression
// 'queryableData.Where(company => (company.ToLower() == "coho winery" || company.Length > 16))'
MethodCallExpression whereCallExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { queryableData.ElementType },
queryableData.Expression,
Expression.Lambda<Func<string, bool>>(predicateBody, new ParameterExpression[] { pe }));
// ***** End Where *****

// ***** OrderBy(company => company) *****
// Create an expression tree that represents the expression
// 'whereCallExpression.OrderBy(company => company)'
MethodCallExpression orderByCallExpression = Expression.Call(
typeof(Queryable),
"OrderBy",
new Type[] { queryableData.ElementType, queryableData.ElementType },
whereCallExpression,
Expression.Lambda<Func<string, string>>(pe, new ParameterExpression[] { pe }));
// ***** End OrderBy *****

// Create an executable query from the expression tree.
IQueryable<string> results = queryableData.Provider.CreateQuery<string>(orderByCallExpression);

// Enumerate the results.
foreach (string company in results)
Console.WriteLine(company);

/* This code produces the following output:

Blue Yonder Airlines
City Power & Light
Coho Winery
Consolidated Messenger
Graphic Design Institute
Humongous Insurance
Lucerne Publishing
Northwind Traders
The Phone Company
Wide World Importers
*/

在 Visual Studio 中调试表达式树

参考:在 Visual Studio 中调试表达式树

参考

System.Linq.Expressions

表达式树 (C#)

由浅入深表达式树(一)创建表达式树