首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 解釋器模式實戰:建構領域專用語言與動態執行引擎

     編輯:關於C#

前言

當系統需要處理特定領域的語法規則(如數學表達式、規則引擎、查詢語言、配置腳本)時,硬編碼解析邏輯會導致系統僵化、難以擴展。解釋器模式提供一種建構「語言解釋器」的架構:定義文法表示,並實作解釋器將句子轉換為可執行操作。本文透過中文語法「{數據庫}[信息](獲取)」動態解析為 C# 程式碼 SqlMessage.Get() 並透過反射執行,完整展示解釋器模式的核心機制與實戰應用。

模式結構與核心角色

  • Context(上下文):Context 類別,攜帶輸入字串 Input 與解釋輸出 Output,作為解釋過程的狀態容器。
  • AbstractExpression(抽象表達式):AbstractExpression,定義 Interpret(Context) 解釋操作,內含字典查表邏輯與抽象方法 GetWord()、GetDictionary() 留待子類別實作。
  • TerminalExpression(終結符表達式):DatabaseExpression、ObjectExpression、MethodExpression,分別解析大括號、中括號、小括號內的關鍵字,並對應至 C# 程式碼片段。
  • Client(客戶端):組裝表達式鏈,依序呼叫 Interpret(),最後透過反射動態執行生成的程式碼。
解釋器模式類別圖

核心程式碼機制深度解析

1. 文法設計與上下文

本範例定義簡單中文文法:

  • {數據庫} → 對應類別名稱前綴(Sql)
  • [信息] → 對應類別名稱主體(Message)
  • (獲取) → 對應方法名稱(.Get())
  • 組合:{數據庫}[信息](獲取) → SqlMessage.Get()

Context 類別攜帶輸入與輸出:

public class Context
{
    public string Input { get; set; }
    public string Output { get; set; }
    public Context(string input) { this.Input = input; }
}

2. 抽象表達式:解釋引擎核心

public abstract class AbstractExpression
{
    public void Interpret(Context context)
    {
        if (String.IsNullOrEmpty(context.Input)) return;
        context.Output += GetCSharp(context.Input);
    }

    private string GetCSharp(string source)
    {
        string word = GetWord(source);              // 子類別實作:提取關鍵字
        GetDictionary().TryGetValue(word, out string csharp); // 子類別實作:字典查表
        return csharp;
    }

    protected abstract string GetWord(string source);           // 正則提取邏輯
    protected abstract Dictionary<string, string> GetDictionary(); // 映射字典
}

模板方法模式應用:Interpret() 定義解釋骨架,GetWord()、GetDictionary() 為可變步驟延遲至子類別。

3. 終結符表達式:三類關鍵字解析器

DatabaseExpression:解析 {...} → 類別前綴

public class DatabaseExpression : AbstractExpression
{
    protected override string GetWord(string source)
    {
        var mc = Regex.Matches(source, @"\{(.*)\}");
        return mc[0].Groups[1].Value; // 取 "數據庫"
    }
    protected override Dictionary<string, string> GetDictionary()
        => new Dictionary<string, string> { { "數據庫", "Sql" } };
}

ObjectExpression:解析 [...] → 類別主體

public class ObjectExpression : AbstractExpression
{
    protected override string GetWord(string source)
        => Regex.Match(source, @"\[(.*)\]").Groups[1].Value; // 取 "信息"
    protected override Dictionary<string, string> GetDictionary()
        => new Dictionary<string, string> { { "信息", "Message" } };
}

MethodExpression:解析 (...) → 方法

public class MethodExpression : AbstractExpression
{
    protected override string GetWord(string source)
        => Regex.Match(source, @"\((.*)\)").Groups[1].Value; // 取 "獲取"
    protected override Dictionary<string, string> GetDictionary()
        => new Dictionary<string, string> { { "獲取", ".Get()" } };
}

4. 客戶端:組裝解釋鏈與動態執行


string chinese = "{數據庫}[信息](獲取)";
Context context = new Context(chinese);

var expressions = new List<AbstractExpression> 
{ 
    new DatabaseExpression(), 
    new ObjectExpression(), 
    new MethodExpression() 
};

foreach (var exp in expressions)
    exp.Interpret(context); // 依序解釋,輸出累積至 context.Output

// context.Output = "SqlMessage.Get()"
// 反射動態執行
var assembly = Assembly.Load("Pattern.Interpreter");
var typeName = context.Output.Split('.')[0];           // "SqlMessage"
var methodName = context.Output.Split('.')[1].Replace("()", ""); // "Get"
var method = assembly.GetType("Pattern.Interpreter." + typeName).GetMethod(methodName);
var result = method.Invoke(null, null); // 靜態方法無需實例

var messages = (List<MessageModel>)result;
Response.Write("中文語法:" + chinese);
Response.Write("解釋後的C#代碼:" + context.Output);
Response.Write("執行結果:" + messages[0].Message);

執行結果驗證

中文語法:{數據庫}[信息](獲取)
解釋後的C#代碼:SqlMessage.Get()
執行結果:SQL方式獲取Message 2007-5-1 8:48:07

完美驗證:中文語法 → 解釋器鏈 → C# 程式碼字串 → 反射執行 → 實際業務結果。

進階架構與擴展方向

非終結符表達式:支援複雜文法結構

本範例僅有終結符(葉子節點)。實際語言常需非終結符(組合節點),如:

  • AndExpression、OrExpression:邏輯組合
  • SequenceExpression:順序執行多個子表達式
  • RepeatExpression:循環執行

非終結符持有子表達式集合,Interpret() 遞迴呼叫子表達式,構建抽象語法樹(AST)。

解析器生成器工具

手寫解釋器適用於簡單文法。複雜語言建議使用:

  • ANTLR:強大的解析器生成器,支援 C# 目標語言。
  • Irony:.NET 專用語言實作工具包。
  • Roslyn:.NET Compiler Platform,可動態編譯執行 C# 程式碼,性能優於反射。

效能優化策略

  • 快取編譯結果:將解釋生成的程式碼/委派快取,避免重複解析與反射。
  • 預編譯運算式樹:使用 System.Linq.Expressions 建構 Lambda 運算式樹並編譯為委派,執行速度接近原生程式碼。
  • 位元組碼解釋:將文法編譯為自定義位元組碼,自行實作虛擬機執行(如 Lua、Python 實作)。

優缺點與適用場景

優點

  • 文法與解釋邏輯分離,易於修改擴展語言語法。
  • 每條文法規則對應一個類別,符合單一職責,易於維護。
  • 支援動態語言特性:執行時解析新腳本、熱更新規則。
  • 可構建內嵌 DSL(領域專用語言),降低業務邏輯表達門檻。

缺點

  • 文法規則多時類別數量爆炸,維護成本高。
  • 解析效能通常低於編譯型語言(可用快取/運算式樹緩解)。
  • 複雜文法(左遞歸、優先級、錯誤恢復)手寫困難。
  • 不適用於通用程式語言實作(建議用成熟解析器生成器)。

經典應用場景

  • 規則引擎:業務人員以自然語言配置風控規則、促銷條件。
  • 報表/查詢建構器:拖拉拽生成 SQL 或查詢 DSL。
  • 數學表達式計算器、配置檔解析、模板引擎。
  • 遊戲腳本系統:設計師編寫劇情腳本,引擎解釋執行。

總結

解釋器模式為「語言處理」提供了標準化架構:Context 攜帶狀態、AbstractExpression 定義解釋骨架、TerminalExpression 實作終結符映射、NonTerminalExpression 組合複雜結構。本範例以中文語法動態生成 C# 程式碼並反射執行為例,展示了從文法定義、正則提取、字典映射、鏈式解釋到動態執行的完整鏈路。雖然手寫解釋器適用於簡單 DSL,但掌握其核心機制(AST、遞迴下降、Context 傳遞)能讓你在需要「可配置、可擴展、動態執行」的業務規則系統中,做出正確的架構選型:簡單用手寫解釋器、複雜上 ANTLR/Roslyn、效能敏感用運算式樹。這正是解釋器模式在現代軟體工程中的實戰價值所在。


參考資源:DoFactory Interpreter Pattern

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved