【演算法】遠方來信,從數學表達式演算法到彙編語法解釋器

来源:https://www.cnblogs.com/lan80/archive/2023/12/04/17872677.html
-Advertisement-
Play Games

Autofac 是一個功能豐富的 .NET 依賴註入容器,用於管理對象的生命周期、解決依賴關係以及進行屬性註入。本文將詳細講解 Autofac 的使用方法,包括多種不同的註冊方式,屬性註入,以及如何使用多個 ContainerBuilder 來註冊和合併組件。我們將提供詳細的源代碼示例來說明每個概念 ...


在繁華的都市中,小悅作為一名軟體工程師,每天都在這座鋼筋水泥的森林里忙碌。她的生活似乎被工作和各種瑣碎的事情填滿了,但在這個繁忙的生活中,她總能在工作之餘找到一些小小的樂趣。

這天下班後,小悅收到了一封來自國外同學蘇菲的email。郵件的內容讓她的思緒一下子飄回了那個學習彙編語言的大學時代。

蘇菲是一個非常聰明的女孩,她們倆在大學時期成為了要好的朋友。蘇菲對編程有著濃厚的興趣,而小悅則是對理論知識情有獨鍾。在大學最後一年的上機考試中,她們倆通過逆波蘭表達式演算法,合作完成了一個數學算式表達式的演算法。

這個算式表達式演算法是小悅在上機考試中實現的,它主要用於解決數學算式計算的問題。但現在回想起來,她覺得這個演算法還有很多可以完善的地方,甚至可以在它基礎上開發出自己的腳本語言解釋器。於是,她決定利用業餘時間來改進這個演算法。

在眾多實用的編程項目中,頁面腳本解釋器和SQL語言解釋器是兩個比較熱門的選擇。但是這兩個項目的語法也相對複雜,因此小悅決定從更簡單的彙編語言解釋器開始研究。

小悅開始仔細研究彙編語言的語法和指令集,她想要用cSharp寫一個自己的彙編語法解釋器。她知道,實現一個彙編語法解釋器並不是一件容易的事情,但她也堅信,只要自己不斷努力和探索,一定能夠成功。

在實現彙編語法解釋器的過程中,小悅參考了大量的資料和文檔,不斷調整和完善自己的代碼。她遇到了很多困難和挫折,但她都沒有放棄。每當遇到問題時,她都會想起蘇菲和那個算式表達式演算法,這讓她有了繼續前進的動力。

經過一段時間的努力,小悅終於成功地寫出了一個自己的彙編語法解釋器。這個解釋器能夠解析彙編語言語法,並根據程式員輸入的語法執行相應的操作。

小悅的閨蜜小欣看到她的成果後,打趣說:“你真是編程界的女俠啊!”小悅聽後笑了笑,心中不禁想起了蘇菲。她想:“如果蘇菲還在國內,我們一定會有更多的合作機會。”

然而,生活總是充滿了未知和變數。蘇菲畢業後選擇了留在國外工作和生活,而小悅則在國內繼續著她的軟體工程師生涯。雖然兩人已經很少聯繫,但小悅一直珍藏著她們之間的友誼和那個上機考試中的算式表達式演算法。

在小悅實現彙編語法解釋器的過程中,她不僅提升了自己的編程能力,還進一步理解了算式表達式演算法的原理和實現方式。她相信,這個經驗將會成為她未來職業生涯中的一筆寶貴財富。

如今的小悅已經不再是那個單純為了應付考試而編程的女孩了。她在編程領域有著自己的追求和夢想。她希望通過自己的努力和不斷的學習,成為一個更加優秀的軟體工程師,為這個數字化時代貢獻自己的力量。


小悅需要實現的彙編解釋器語法如下:

mov x, y - 將y(無論是整數還是寄存器的值)複製到寄存器(變數)x中。
inc x - 使寄存器x的內容增加1。
dec x - 使寄存器x的內容減少1。
add x, y - 將寄存器x的內容與y(無論是整數還是寄存器的值)相加,並將結果存儲在x中(即register[x] += y)。
sub x, y - 從寄存器x中減去y(無論是整數還是寄存器的值),並將結果存儲在x中(即register[x] -= y)。
mul x, y - 與乘法相同(即register[x] *= y)。
div x, y - 與整數除法相同(即register[x] /= y)。
label: - 定義函數標簽位置,一般用於函數定位(標簽 = 標識符 + 冒號,標識符是與任何其他命令不匹配的字元串)。跳轉命令和調用針對程式中的這些標簽位置。
jmp lbl - 跳轉到自定義函數標簽lbl。
cmp x, y - 比較x(無論是整數還是寄存器的值)和y(無論是整數還是寄存器的值)。結果用於條件跳轉(jne,je,jge,jg,jle和jl)。
jne lbl - 如果上一個cmp命令的值不相等,則跳轉到標簽lbl。
je lbl - 如果上一個cmp命令的值相等,則跳轉到標簽lbl。
jge lbl - 如果在之前的cmp命令中x大於或等於y,則跳轉到標簽lbl。
jg lbl - 如果在之前的cmp命令中x大於y,則跳轉到標簽lbl。
jle lbl - 如果在之前的cmp命令中x小於或等於y,則跳轉到標簽lbl。
jl lbl - 如果在之前的cmp命令中x小於y,則跳轉到標簽lbl。
call lbl - 調用由lbl標識的子程式。當在子程式中找到ret時,指令指針應返回到此call命令之後的指令。
ret - 當在子程式中找到ret時,指令指針應返回到調用當前函數的指令。
msg '輸出結果:  ',x - 這條指令存儲程式的輸出。它可能包含文本字元串(以單引號分隔)和寄存器。參數的數量不限,會因程式而異。
end - 這條指令表示程式正確結束,因此返回存儲的輸出(如果程式在沒有此指令的情況下終止,它應返回預設輸出:參見下文)。
; - comment註釋指令在代碼執行程式時不運行。

示例語法:

//代碼定義
var program = @"
; My first program
mov a, 5
inc a
call function
msg '計算結果:(5+1)/2 = ', a ; output message
end

function:
div a, 2
ret
";
//執行代碼
AssemblerInterpreter.Interpret(program);
//msg命令輸出結果 
計算結果:(5+1)/2 = 3


演算法實現1:

  1 public class AssemblerInterpreter
  2 {
  3      public static string Interpret(string input)
  4     {
  5       // 存儲寄存器的字典
  6       var register = new Dictionary<string, int>();
  7       // 存儲比較結果的字元串
  8       var compare  = "";
  9       // 存儲標簽的字典
 10       var label    = new Dictionary<string, int>();
 11       // 整數類型的棧,存儲代碼當前位置
 12       var stack    = new Stack<int>();
 13       // 存儲消息的字元串構建器
 14       var messages = new StringBuilder();
 15       
 16       // 將輸入的程式按行分割
 17       var program = input.Split("\n");
 18       
 19       // 遍歷程式,找到標簽並存儲其位置
 20       for(var i = 0; i < program.Length; i++)
 21       {
 22         var parts = program[i].Split(":");
 23         if (parts.Length == 2) label.Add(parts[0], i);
 24       }
 25         
 26       // 遍歷程式的每一行指令
 27       for (var i = 0; i < program.Length; i++)
 28       {
 29         // 將當前行的指令解析為操作符和操作數
 30         var token = TokensFrom(program[i]);
 31         if (token.Length == 0) continue; // 跳過空行
 32         
 33         // 根據操作符執行相應的操作
 34         if (token[0] == "mov")
 35         {
 36           // 將操作數存儲到寄存器中
 37           var r = token[1];
 38           var val = ValueOf(token[2]);
 39           if (register.ContainsKey(r)) register[r] = val;
 40           else register.Add(r, val);
 41         }
 42         else if (token[0] == "inc") register[token[1]++;
 43         else if (token[0] == "dec") register[token[1]--;
 44         else if (token[0] == "add") register[token[1]] += ValueOf(token[2]);
 45         else if (token[0] == "sub") register[token[1]] -= ValueOf(token[2]);
 46         else if (token[0] == "mul") register[token[1]] *= ValueOf(token[2]);
 47         else if (token[0] == "div") register[token[1]] /= ValueOf(token[2]);
 48         else if (token[0] == "msg")
 49         {
 50           // 將消息內容添加到消息字元串構建器中
 51           var args = ParseMsg(program[i])
 52             .Select(s => s.First() == '\''
 53                       ? s.Length >= 2 ? s[1..^1] : s
 54                       : ValueOf(s).ToString());
 55           messages.Append(string.Concat(args));
 56         }
 57         else if (token[0] == "call")
 58         {
 59           // 將當前位置壓入棧中,並跳轉到指定標簽位置
 60           stack.Push(i);
 61           i = label[token[1]];
 62         }
 63         else if (token[0] == "ret") i = stack.Pop(); // 從棧中彈出位置並跳轉
 64         else if (token[0] == "cmp")
 65         {
 66           // 比較兩個值並存儲比較結果
 67           var x = ValueOf(token[1]);
 68           var y = ValueOf(token[2]);
 69           
 70           if (x == y) compare = "=";
 71           else if (x > y) compare = ">";
 72           else if (x < y) compare = "<";
 73           else compare = "!=";
 74         }
 75         // 根據比較結果進行條件跳轉
 76         else if (token[0] == "jmp") i = label[token[1]];
 77         else if (token[0] == "jne")
 78         {
 79           if (compare != "=") i = label[token[1]];
 80         }
 81         else if (token[0] == "je")
 82         {
 83           if (compare == "=") i = label[token[1]];
 84         }
 85         else if (token[0] == "jge")
 86         {
 87           if (compare == ">" || compare == "=") i = label[token[1]];
 88         }
 89         else if (token[0] == "jg")
 90         {
 91           if (compare == ">") i = label[token[1]];
 92         }
 93         else if (token[0] == "jle")
 94         {
 95           if (compare == "<" || compare == "=") i = label[token[1]];
 96         }
 97         else if (token[0] == "jl")
 98         {
 99           if (compare == "<") i = label[token[1]];
100         }
101         else if (token[0] == "end") return messages.ToString();
102       }
103 
104       return null;
105 
106       // 從輸入的行中提取標記並返回標記數組
107       string[] TokensFrom(string line) =>
108         Regex.Split(RemoveComment(line), "[ ,]+")  // 使用正則表達式分割去除註釋後的行,並去除空格
109             .Select(s => s.Trim())  // 去除每個標記的首尾空格
110             .Where(s => !string.IsNullOrEmpty(s))  // 去除空字元串
111             .ToArray();  // 轉換為數組
112 
113         // 解析消息內容並返回消息參數數組
114         string[] ParseMsg(string line)
115         {
116             var args = Regex.Replace(line, @"^\s*msg\s*", "");  // 去除消息指令並獲取消息內容
117             var result = new List<string>();  // 創建存儲結果的列表
118 
119             var token = new StringBuilder();  // 創建 StringBuilder 存儲當前標記
120             var inQuote = false;  // 標記是否在引號內
121             foreach (var c in args)  // 遍歷消息內容的每個字元
122             {
123                 if (c == '\'' && inQuote)  // 如果是引號且在引號內
124                 {
125                     inQuote = false;  // 標記離開引號狀態
126                     token.Append(c);  // 將引號添加到標記中
127                 }
128                 else if (c == '\'' && !inQuote)  // 如果是引號且不在引號內
129                 {
130                     inQuote = true;  // 標記進入引號狀態
131                     token.Append(c);  // 將引號添加到標記中
132                 }
133                 else if (c == ',' && !inQuote)  // 如果是逗號且不在引號內
134                 {
135                     result.Add(token.ToString().Trim());  // 將當前標記去除首尾空格後添加到結果列表
136                     token.Clear();  // 清空當前標記
137                 }
138                 else if (c == ' ' && !inQuote) continue;  // 如果是空格且不在引號內,則繼續下一次迴圈
139                 else if (c == ';' && !inQuote) break;  // 如果是分號且不在引號內,則結束迴圈
140                 else token.Append(c);  // 其他情況將字元添加到當前標記中
141             }
142             if (token.Length > 0) result.Add(token.ToString().Trim());  // 將最後一個標記去除首尾空格後添加到結果列表
143             return result.ToArray();  // 轉換為數組並返回
144         }
145 
146         // 去除行中的註釋並返回處理後的行
147         string RemoveComment(string line) => Regex.Replace(line, ";.*", "");  // 使用正則表達式去除分號及其後的內容
148 
149         // 獲取變數的值,如果是整數則直接返回,否則從寄存器中獲取
150         int ValueOf(string x) => int.TryParse(x, out var r) ? r : register[x];
151     }
152 }

這段代碼實現了一個基於指令集的虛擬機執行演算法解釋器。它解釋並定義了一系列基本的彙編指令,包括mov、inc、dec、add、sub、mul、div、msg、call、ret、cmp、jmp、jne、je、jge、jg、jle、jl和end。這些指令可以操作寄存器、棧、標簽和消息,並且可以進行條件跳轉和比較操作。

這個演算法是一個彙編解釋器,它模擬了虛擬機的功能。在演算法中,通過使用字典存儲寄存器的值、比較結果的字元串、標簽的位置等信息,以及使用棧存儲代碼當前位置,來模擬虛擬機的處理器、記憶體、寄存器等功能。演算法遍歷輸入的程式,解析每一行指令並執行相應的操作,如將操作數存儲到寄存器中、進行加減乘除運算、比較兩個值等。同時,演算法還實現了條件跳轉和消息輸出等功能,以模擬虛擬機的指令集。通過這種方式,演算法實現了對輸入程式的解釋和執行,從而實現了對虛擬機的模擬。這樣的虛擬機模擬可以使程式在不同的平臺上運行,提高了程式的靈活性和可移植性。

虛擬機程式AssemblerInterpreter.Interpret作為一個.net宿主程式,接收並執行這些外部輸入的彙編語言代碼。

該演算法首先接收一個彙編語法表達式的字元串作為輸入,然後通過解析字元串,逐個獲取操作數和操作符。在解析過程中,演算法會根據操作符的類型和操作數的值,進行相應的計算操作。這些操作包括將指令位置壓入棧中、從棧中彈出指令在寄存器中的位置併進行計算等。

演算法的核心部分是遍歷語法表達式並執行相應的操作。在這個過程中,演算法會逐個解析字元串中的字元,並根據解析的結果執行相應的計算步驟。一旦完成計算,演算法會將最終的結果存儲在棧頂,而棧頂的元素就是最終的指令計算結果。

註1:else if (token[0] == "ret") i = stack.Pop(); // 從棧中彈出位置並跳轉

這行代碼是解釋器中處理ret指令的部分。當解釋器遇到ret指令時,它需要從調用棧中彈出一個位置並跳轉到該位置。

讓我們來解釋一下這行代碼的邏輯:

  1. 首先,解釋器解析到ret指令,並將其作為一個操作符token
  2. 接著,解釋器檢查token[0]是否等於"ret",即檢查標記的第一個元素是否是"ret"。這是為了確保當前指令是ret指令。
  3. 如果token[0]等於"ret",則執行i = stack.Pop(),這意味著從調用棧stack中彈出一個位置,並將其賦值給程式計數器i。這樣程式計數器將跳轉到之前調用指令的下一條指令位置,實現了ret指令的跳轉功能。

註2:在代碼47行,操作符分支處理中,可以根據實際情況擴展新的賦值函數

else if (token[0] == "div") register[token[1]] /= ValueOf(token[2]);
else if (token[0] == "expression") ... //在這裡可擴展其他語法功能,除了加減乘除,還可以添加逆波蘭表達式演算法進行變數賦值或其他自定義函數處理等


基於指令集的虛擬機執行演算法的歷史故事可以追溯到電腦科學和軟體工程的早期發展階段,它經歷了多個關鍵的里程碑和發展趨勢:

1. 早期電腦:在早期的電腦系統中,程式員需要直接編寫機器碼指令,這些指令直接操作電腦的硬體。這種方式對於程式員來說非常繁瑣和複雜,因此人們開始尋找更高級的抽象方式來編寫程式。這個時期,一些先驅者提出了彙編語言,它提供了一種更接近硬體的編程方式,使得程式員可以更容易地編寫機器碼指令。
2. 彙編語言:為了簡化程式員編寫機器碼指令的工作,彙編語言被引入。彙編語言是一種低級的編程語言,它使用助記符(mnemonics)來代替機器碼指令,並提供了一些符號標簽來簡化跳轉和調用等操作。這使得程式員可以更加高效地編寫程式,減少了出錯的可能性。
    高級語言:1957年:IBM的約翰·巴科斯(John Backus)創建全世界第一套高級語言:Fortran(formula translate)。Fortran的跨平臺限制主要是由於其與特定的操作系統和硬體架構的緊密相關性,如windows/linux,以及不同編譯器和平臺的差異和限制。如果需要在不同的操作系統或硬體平臺上運行Fortran程式,可能需要重新編譯或修改程式,以確保它能夠在目標平臺上正確運行。

3. 虛擬機:為了實現跨平臺的程式執行,虛擬機概念被引入。虛擬機是一個軟體實體,它模擬了一臺電腦,包括處理器、記憶體、寄存器等,並提供了一組指令集。程式員可以使用這個指令集來編寫程式,併在虛擬機上執行,如JVM,而不需要關心底層硬體和操作系統。虛擬機的引入使得程式可以在不同的平臺上運行,提高了程式的靈活性和可移植性。
4. 基於指令集的虛擬機執行演算法:虛擬機中的核心部分是基於指令集的虛擬機執行演算法。它負責解釋和執行程式中的指令,包括數據操作、代碼跳轉、函數調用等。執行演算法通常包括指令解析、寄存器操作、記憶體訪問、跳轉控制等步驟。這個演算法的實現除了完成基本指令需求,還需要考慮性能、安全性和穩定性等方面的問題。
5. 發展趨勢:隨著電腦科學和軟體工程的發展,基於指令集的虛擬機執行演算法得到了不斷的改進和優化。例如,引入了即時編譯(JIT)技術、增強了指令集、優化了執行引擎等,使得虛擬機執行演算法在性能和功能上得到了顯著提升。同時,隨著雲計算和移動互聯網的普及,基於指令集的虛擬機執行演算法也在這些領域得到了廣泛的應用和發展。


通過測試用例(5+1)/2,我們將執行AssemblerInterpreter.Interpret(program)來運行這段程式,並期望msg命令輸出"計算結果:(5+1)/2 = 3"。

程式的執行步驟如下:

  1. 首先,AssemblerInterpreter.Interpret(program)將解釋器應用於給定的程式字元串program
  2. 解釋器將逐行解析程式字元串,並執行相應的操作。在解釋器中,會調用TokensFrom函數來解析每一行的標記。
  3. 對於每個標記,解釋器將執行相應的操作,比如mov指令會移動一個值5到一個寄存器,inc指令會增加寄存器中的值,即(5+1),call指令會調用一個函數function,即(5+1)/2,msg指令會輸出常量消息和a的值3。
  4. 在執行msg指令時,解釋器會調用ParseMsg函數來解析消息內容,並輸出最終的消息結果。

在這個測試用例中,當msg指令被執行時,ParseMsg函數會解析消息內容"'計算結果:(5+1)/2 = ', a",並輸出"計算結果:(5+1)/2 = 3"作為最終結果。

因此,通過執行AssemblerInterpreter.Interpret(program),程式將按預期輸出"計算結果:(5+1)/2 = 3"。


演算法實現2:

  1 public class AssemblerInterpreter
  2 {
  3     public static string Interpret(string input)
  4     {
  5         Regex trimmer = new Regex(@"\s\s+");
  6         string[] unparsedInstructions = input.Split("\n");
  7         Dictionary<string, int> registers = new Dictionary<string, int>();
  8         Dictionary<string, int> labels = new Dictionary<string, int>();
  9         IInstruction[] instructions = new IInstruction[unparsedInstructions.Length];
 10         int endOfProgram = 0;
 11         //cast to instructions
 12         for(int i = 0; i < unparsedInstructions.Length;  i++)
 13         {
 14             string cleanString = unparsedInstructions[i].TrimStart();
 15             cleanString = trimmer.Replace(cleanString, " ");
 16             string[] instructionParts = cleanString.Split(" ");
 17             switch(instructionParts[0]){
 18                 case ";":
 19                     instructions[i] = new Idle();
 20                     break;
 21                 case "msg":
 22                     instructions[i] = new Msg(cleanString.Substring(4));
 23                     break;
 24                 case "call":
 25                     instructions[i] = new Call(instructionParts[1]);
 26                     break;
 27                 case "mov":
 28                     instructions[i] = new Mov(instructionParts[1].Remove(1), new Arguement(instructionParts[2]));
 29                     break;
 30                 case "inc":
 31                     instructions[i] = new Inc(instructionParts[1]);
 32                     break;
 33                 case "dec":
 34                     instructions[i] = new Dec(instructionParts[1]);
 35                     break;
 36                 case "add":
 37                     instructions[i] = new Add(instructionParts[1].Remove(1), new Arguement(instructionParts[2]));
 38                     break;
 39                 case "sub":
 40                     instructions[i] = new Sub(instructionParts[1].Remove(1), new Arguement(instructionParts[2]));
 41                     break;
 42                 case "div":
 43                     instructions[i] = new Div(instructionParts[1].Remove(1), new Arguement(instructionParts[2]));
 44                     break;
 45                 case "mul":
 46                     instructions[i] = new Mul(instructionParts[1].Remove(1), new Arguement(instructionParts[2]));
 47                     break;
 48                 case "cmp":
 49                     instructions[i] = new Cmp(new Arguement(instructionParts[1].Remove(1)), new Arguement(instructionParts[2]));
 50                     break;
 51                 case "jmp":
 52                     instructions[i] = new Jmp(instructionParts[1]);
 53                     break;
	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 記憶體是在程式中打交道不可缺少的存在,或者在GC語言中記憶體概念會被刻意的屏蔽掉,但如果是棧遞歸函數調用自身這種情況,stack overflow這種情況是一樣的會碰到的,所以瞭解下堆與棧碰到問題的時候好解決問題 ...
  • 在.NET Core中,使用Action和Options參數方式配置服務並將配置信息對象註冊到IServiceCollection的好處在於,它提供了更高級別的可配置性和可擴展性。這種模式允許將配置信息與服務的實現分離,使配置更加模塊化和可管理。通過將配置信息對象註冊到IServiceCollect ...
  • 簡介 從C# 3.0起很少需要自己聲明委托。System.Func 是一個泛型委托,它可以表示帶有返回值的方法。它可以接受一個到多個輸入參數,並返回一個指定類型的結果。System.Func 委托的最後一個類型參數表示方法的返回值類型。而System.Action系列代表返回void的方法。 Fun ...
  • 這是我在C#中第一次用到json,以前都用別的替代,但是瞭解了之後發現這個是真的好用。 首先,有幾個網站先貼上來保存一下。 JSON 模式驗證器 - Newtonsoft (jsonschemavalidator.net),顧名思義,就是驗證我們的json格式是否正確。 Introduction ( ...
  • 在本示例中,我們將使用Autofac和AspectC(Autofac.Extras.DynamicProxy2)來演示如何實現AOP(面向切麵編程)。我們將創建一個簡單的C#控制台應用程式,並應用AOP以解決日誌記錄的問題。首先,讓我們討論AOP的用途和目標。 AOP(面向切麵編程)的用途 AOP是 ...
  • 在我們很多應用系統中,往往都需要根據實際情況生成一些編碼規則,如訂單號、入庫單號、出庫單號、退貨單號等等,我們有時候根據規則自行增加一個函數來生成處理,不過我們仔細觀察後,發現它們的編碼規則有很大的共通性,因此可以考慮使用一些通用的業務編碼規則生成,從而在系統中統一維護即可,本篇隨筆介紹如何在WIn... ...
  • 一:背景 1. 講故事 在高級調試的旅程中,經常會有一些朋友問我什麼是 工作集(記憶體),什麼是 提交大小,什麼是 Virtual Size, 什麼是 Working Set 。。。截圖如下: 既然有很多朋友問,這些用口頭也不怎麼好描述,剛好上午有時間就系統的聊一下吧。 二:記憶體術語解讀 1. Vir ...
  • 推薦演算法是機器學習和數據挖掘領域的重要組成部分,用於為用戶提供個性化推薦內容。在.NET中,可以使用不同的演算法來實現推薦系統。在本文中,我將介紹三種常見的推薦演算法:協同過濾、內容過濾和深度學習推薦系統,並提供相應的.NET源代碼示例。 協同過濾推薦演算法 協同過濾演算法基於用戶行為數據,通過分析用戶之間 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 推薦一款基於.NET 8、WPF、Prism.DryIoc、MVVM設計模式、Blazor以及MySQL資料庫構建的企業級工作流系統的WPF客戶端框架-AIStudio.Wpf.AClient 6.0。 項目介紹 框架採用了 Prism 框架來實現 MVVM 模式,不僅簡化了 MVVM 的典型 ...
  • 先看一下效果吧: 我們直接通過改造一下原版的TreeView來實現上面這個效果 我們先創建一個普通的TreeView 代碼很簡單: <TreeView> <TreeViewItem Header="人事部"/> <TreeViewItem Header="技術部"> <TreeViewItem He ...
  • 1. 生成式 AI 簡介 https://imp.i384100.net/LXYmq3 2. Python 語言 https://imp.i384100.net/5gmXXo 3. 統計和 R https://youtu.be/ANMuuq502rE?si=hw9GT6JVzMhRvBbF 4. 數 ...
  • 本文為大家介紹下.NET解壓/壓縮zip文件。雖然解壓縮不是啥核心技術,但壓縮性能以及進度處理還是需要關註下,針對使用較多的zip開源組件驗證,給大家提供個技術選型參考 之前在《.NET WebSocket高併發通信阻塞問題 - 唐宋元明清2188 - 博客園 (cnblogs.com)》講過,團隊 ...
  • 之前寫過兩篇關於Roslyn源生成器生成源代碼的用例,今天使用Roslyn的代碼修複器CodeFixProvider實現一個cs文件頭部註釋的功能, 代碼修複器會同時涉及到CodeFixProvider和DiagnosticAnalyzer, 實現FileHeaderAnalyzer 首先我們知道修 ...
  • 在軟體行業,經常會聽到一句話“文不如表,表不如圖”說明瞭圖形在軟體應用中的重要性。同樣在WPF開發中,為了程式美觀或者業務需要,經常會用到各種個樣的圖形。今天以一些簡單的小例子,簡述WPF開發中幾何圖形(Geometry)相關內容,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 在 C# 中使用 RabbitMQ 通過簡訊發送重置後的密碼到用戶的手機號上,你可以按照以下步驟進行 1.安裝 RabbitMQ 客戶端庫 首先,確保你已經安裝了 RabbitMQ 客戶端庫。你可以通過 NuGet 包管理器來安裝: dotnet add package RabbitMQ.Clien ...
  • 1.下載 Protocol Buffers 編譯器(protoc) 前往 Protocol Buffers GitHub Releases 頁面。在 "Assets" 下找到適合您系統的壓縮文件,通常為 protoc-{version}-win32.zip 或 protoc-{version}-wi ...
  • 簡介 在現代微服務架構中,服務發現(Service Discovery)是一項關鍵功能。它允許微服務動態地找到彼此,而無需依賴硬編碼的地址。以前如果你搜 .NET Service Discovery,大概率會搜到一大堆 Eureka,Consul 等的文章。現在微軟為我們帶來了一個官方的包:Micr ...
  • ZY樹洞 前言 ZY樹洞是一個基於.NET Core開發的簡單的評論系統,主要用於大家分享自己心中的感悟、經驗、心得、想法等。 好了,不賣關子了,這個項目其實是上班無聊的時候寫的,為什麼要寫這個項目呢?因為我單純的想吐槽一下工作中的不滿而已。 項目介紹 項目很簡單,主要功能就是提供一個簡單的評論系統 ...