System.CodeDom 命名空間提供語言無關的程式碼物件模型,讓你在執行期以物件圖構建原始碼,再由特定語言提供者 (CSharpCodeProvider/VBCodeProvider) 輸出對應語言檔案。本文完整示範如何用 CodeDom 自動生成一個含連線管理、參數化查詢的 PowerDataAccess 類別,並解析關鍵節點與擴展策略。
using System;
using System.Data;
using System.Data.SqlClient;
namespace Power.Cuike519
{
public class PowerDataAccess
{
private SqlConnection m_connection;
public PowerDataAccess() { }
public virtual SqlConnection Connection
{
get => m_connection;
set => m_connection = value;
}
public virtual DataSet GetAllAuthors(string s_State)
{
try
{
if (m_connection != null)
{
var da = new SqlDataAdapter(
string.Format("select * from authors where state like '{0}'", s_State),
m_connection);
var ds = new DataSet();
da.Fill(ds);
return ds;
}
return null;
}
catch (System.ApplicationException ex) { throw ex; }
finally { m_connection.Close(); }
}
}
}
| 目標結構 | CodeDom 類別 | 命名空間 |
|---|---|---|
| 編譯單元/using | CodeSnippetCompileUnit / CodeNamespaceImport | System.CodeDom |
| 命名空間 | CodeNamespace | System.CodeDom |
| 類別宣告 | CodeTypeDeclaration | System.CodeDom |
| 欄位 | CodeMemberField | System.CodeDom |
| 屬性 | CodeMemberProperty (GetStatements/SetStatements) | System.CodeDom |
| 建構函式 | CodeConstructor | System.CodeDom |
| 方法 | CodeMemberMethod | System.CodeDom |
| 參數 | CodeParameterDeclarationExpression | System.CodeDom |
| try/catch/finally | CodeTryCatchFinallyStatement | System.CodeDom |
| if 條件 | CodeConditionStatement | System.CodeDom |
| 區域變數 | CodeVariableDeclarationStatement | System.CodeDom |
| 方法呼叫 | CodeMethodInvokeExpression | System.CodeDom |
| return/throw | CodeMethodReturnStatement / CodeThrowExceptionStatement | System.CodeDom |
| this 參考 | CodeThisReferenceExpression | System.CodeDom |
| 欄位參考 | CodeFieldReferenceExpression | System.CodeDom |
| 屬性值參考 | CodePropertySetValueReferenceExpression | System.CodeDom |
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.IO;
using Microsoft.CSharp;
public class DalCodeGenerator
{
public void Generate(string outputPath = "PowerDataAccess.cs")
{
// 1. 建立輸出流與 C# 提供者
using (var stream = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
using (var writer = new StreamWriter(stream))
{
var provider = new CSharpCodeProvider();
var options = new CodeGeneratorOptions
{
BracingStyle = "C", // 大括號換行
IndentString = " ", // 4 空格縮排
BlankLinesBetweenMembers = true
};
// 2. 建立編譯單元:using 指令
var compileUnit = new CodeCompileUnit();
var ns = new CodeNamespace("Power.Cuike519");
compileUnit.Namespaces.Add(ns);
ns.Imports.Add(new CodeNamespaceImport("System"));
ns.Imports.Add(new CodeNamespaceImport("System.Data"));
ns.Imports.Add(new CodeNamespaceImport("System.Data.SqlClient"));
// 3. 類別宣告
var cls = new CodeTypeDeclaration("PowerDataAccess")
{
IsClass = true,
TypeAttributes = System.Reflection.TypeAttributes.Public
};
ns.Types.Add(cls);
// 4. 私有欄位:SqlConnection m_connection
cls.Members.Add(new CodeMemberField
{
Name = "m_connection",
Type = new CodeTypeReference("SqlConnection"),
Attributes = MemberAttributes.Private
});
// 5. 預設建構函式
cls.Members.Add(new CodeConstructor
{
Attributes = MemberAttributes.Public
});
// 6. 屬性:Connection (虛擬、公開、get/set)
var prop = new CodeMemberProperty
{
Name = "Connection",
Type = new CodeTypeReference("SqlConnection"),
Attributes = MemberAttributes.Public | MemberAttributes.Virtual
};
// get { return this.m_connection; }
prop.GetStatements.Add(new CodeMethodReturnStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_connection")));
// set { this.m_connection = value; }
prop.SetStatements.Add(new CodeAssignStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_connection"),
new CodePropertySetValueReferenceExpression()));
cls.Members.Add(prop);
// 7. 方法:GetAllAuthors
var method = new CodeMemberMethod
{
Name = "GetAllAuthors",
ReturnType = new CodeTypeReference("DataSet"),
Attributes = MemberAttributes.Public | MemberAttributes.Virtual
};
method.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "s_State"));
// try 區塊
var tryBlock = new CodeTryCatchFinallyStatement();
// if (this.m_connection != null)
var ifStmt = new CodeConditionStatement
{
Condition = new CodeBinaryOperatorExpression(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_connection"),
CodeBinaryOperatorType.IdentityInequality,
new CodePrimitiveExpression(null))
};
// 真分支:建立 SqlDataAdapter、DataSet、Fill、return
// SqlDataAdapter da = new SqlDataAdapter(string.Format(...), this.m_connection);
var daVar = new CodeVariableDeclarationStatement
{
Name = "da",
Type = new CodeTypeReference("SqlDataAdapter"),
InitExpression = new CodeObjectCreateExpression(
new CodeTypeReference("SqlDataAdapter"),
new CodeMethodInvokeExpression(
new CodeTypeReferenceExpression("string"), "Format",
new CodePrimitiveExpression("select * from authors where state like '{0}'"),
new CodeVariableReferenceExpression("s_State")),
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_connection"))
};
ifStmt.TrueStatements.Add(daVar);
// DataSet ds = new DataSet();
var dsVar = new CodeVariableDeclarationStatement
{
Name = "ds",
Type = new CodeTypeReference("DataSet"),
InitExpression = new CodeObjectCreateExpression(new CodeTypeReference("DataSet"))
};
ifStmt.TrueStatements.Add(dsVar);
// da.Fill(ds);
ifStmt.TrueStatements.Add(new CodeExpressionStatement(
new CodeMethodInvokeExpression(
new CodeVariableReferenceExpression("da"), "Fill",
new CodeVariableReferenceExpression("ds"))));
// return ds;
ifStmt.TrueStatements.Add(new CodeMethodReturnStatement(
new CodeVariableReferenceExpression("ds")));
// 假分支:return null;
ifStmt.FalseStatements.Add(new CodeMethodReturnStatement(
new CodePrimitiveExpression(null)));
tryBlock.TryStatements.Add(ifStmt);
// catch (ApplicationException ex) { throw ex; }
var catchClause = new CodeCatchClause("ex", new CodeTypeReference("System.ApplicationException"));
catchClause.Statements.Add(new CodeThrowExceptionStatement(
new CodeVariableReferenceExpression("ex")));
tryBlock.CatchClauses.Add(catchClause);
// finally { this.m_connection.Close(); }
tryBlock.FinallyStatements.Add(new CodeExpressionStatement(
new CodeMethodInvokeExpression(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), "m_connection"),
"Close")));
method.Statements.Add(tryBlock);
cls.Members.Add(method);
// 8. 生成代碼
provider.GenerateCodeFromCompileUnit(compileUnit, writer, options);
writer.Flush();
}
Console.WriteLine($"Generated: {Path.GetFullPath(outputPath)}");
}
}
CodeSnippetCompileUnit:直接寫入原始字串 (如 using System;),不支援語言無關轉換CodeCompileUnit + CodeNamespaceImport:標準物件模型,**支援多語言輸出** (C#/VB/F#)CodeDom 採用表達式導向:每個運算 (new、呼叫、二元運算、索引) 都是節點,遞迴組合形成 AST。優點:語言無關;缺點:冗長、需大量 new CodeXXXExpression()。
// 產生:string.Format("select * from authors where state like '{0}'", s_State)
new CodeMethodInvokeExpression(
new CodeTypeReferenceExpression("string"), "Format",
new CodePrimitiveExpression("select * from authors where state like '{0}'"),
new CodeVariableReferenceExpression("s_State"))
catch { throw ex; } 會**重設堆疊追蹤**,建議改 throw; (CodeDom 用 CodeThrowExceptionStatement(null) 表示 rethrow)finally 中關閉連線前應檢查 State != ConnectionState.Closed| 層次 | 資料來源 | 生成目標 |
|---|---|---|
| Level 1 | 硬編碼 (本文) | 單一 DAL 類別 |
| Level 2 | XML/JSON 組態 | 多實體 CRUD 類別 |
| Level 3 | 資料庫 Schema (INFORMATION_SCHEMA) | 完整 ORM 實體 + Repository |
| Level 4 | Roslyn Syntax API | 增量原始碼生成器 (Source Generator) |
| 問題 | 原因 | 解決 |
|---|---|---|
| 生成代碼縮排混亂 | CodeGeneratorOptions 未設定 | 設定 IndentString、BracingStyle |
| 缺少 using | 未加 CodeNamespaceImport | 在 CodeNamespace.Imports 加入 |
| 型別參考錯誤 | CodeTypeReference 字串拼寫錯誤 | 使用 typeof(T).FullName 或完整命名空間 |
| 編譯生成的代碼報錯 | CodeDom 邏輯與目標語言語法不符 | 先生成檔案再手動編譯驗證,調整節點結構 |
結語:CodeDom 雖屬較舊技術,但其語言無關 AST 建模思想值得深入理解。掌握 CodeDom,等於掌握了「以程式碼寫程式碼」的元編程核心能力。現代專案建議轉向 Roslyn Source Generator,享受編譯期型別安全與零開銷優勢;但若需執行期動態組裝並編譯 (如規則引擎、腳本系統),CodeDom 仍是可靠選擇。