在Visual Studio .NET中,一個解決方案可以包含多個項目,一個項目可以引用若干其它項目。編譯的時候,VS會自動確定每個項目的編譯順序。VS究竟是如何計算出這個順序的呢?
如果學習過數據結構,可以很容易回答出這個問題:拓撲排序(Topological Sort)。
什么是拓撲排序?讓我們來溫習一下。百度百科上的介紹如下:
對一個有向無環圖(Directed Acyclic Graph簡稱DAG)G進行拓撲排序,是將G中所有頂點排成一個線性序列,使得圖中任意一對頂點u和v,若<u,v> ∈E(G),則u在線性序列中出現在v之前。
上述介紹抽象,不如用實際案例來解釋一下。假如在VS中創建一個MVC的解決方案XMedia,該解決方案包含的項目,以及項目之間的引用關系如下表所示:
項目 |
引用 |
XMedia |
XMedia.Controllers、XMedia.Models、XMedia.Logics、XMedia.Commons |
XMedia.Controllers |
XMedia.Models、XMedia.Logics、XMedia.Commons |
XMedia.Models |
|
XMedia.Logics |
XMedia.Models、XMedia.Commons |
XMedia.Commons |
|
項目之間的引用關系,是一種依賴關系。如果項目A引用項目B,則表示A依賴B。所以,必須先編譯項目B,再編譯項目A。
根據經驗,我們可以得出上述項目的編譯順序依次是:XMedia.Commons、XMedia.Models、XMedia.Logics、XMedia.Controllers、XMedia。當然,也可以把前兩項對調一下。
項目和引用關系構成了一張有向圖圖,項目相當於有向圖中的頂點(Vertex),引用關系相當於有向圖中的邊(Edge),而項目的編譯順序就是一個拓撲序列,產生該序列的算法稱為拓撲排序算法。
以下是項目引用關系的有向圖展示:
拓撲排序算法的簡要描述:
(1) 從有向圖中選擇一個出度為0的頂點並且輸出它。
(2) 從圖中刪去該頂點,並且刪去該頂點的所有邊。
(3) 重復上述兩步,直到剩余的圖中沒有出度為0的頂點。
按照上述算法,運行過程演示如下:
第一步 選擇 XMedia.Commons節點
第二步 選擇XMedia.Models節點
第三步 選擇XMedia.Logics節點
第四步 選擇XMedia.Controllers節點
第五步 選擇XMedia節點
接下來我們用C#實現代碼實現這個算法。
由於拓撲排序是一個應用很多的算法,所以,我們將實現一個通用的排序算法。在這個通用的算法中,我們將頂點之間的關系作為依賴關系。代碼如下:
using System; using System.Collections.Generic; using System.Linq; namespace ConsoleApplication1 { /// <summary> /// 拓撲排序類。 /// </summary> public class TopologicSort { /// <summary> /// 拓撲順序。 /// </summary> /// <typeparam name="TKey">節點的鍵值類型。</typeparam> /// <param name="nodes">一組節點。</param> /// <returns>拓撲序列。</returns> /// <exception cref="InvalidOperationException">如果存在雙向引用或循環引用,則拋出該異常。</exception> public IEnumerable<string> OrderBy(IEnumerable<TopologicNode> nodes) { if (nodes == null) yield break; //復制一份,便於操作 List<TopologicNode> list = new List<TopologicNode>(); foreach (var item in nodes) { TopologicNode node = new TopologicNode() { Key = item.Key }; if (item.Dependences != null) node.Dependences = new List<string>(item.Dependences); list.Add(node); } while (list.Count > 0) { //查找依賴項為空的節點 var item = list.FirstOrDefault(c => c.Dependences == null || c.Dependences.Count == 0); if (item != null) { yield return item.Key; //移除用過的節點,以及與其相關的依賴關系 list.Remove(item); foreach (var otherNode in list) { if (otherNode.Dependences != null) otherNode.Dependences.Remove(item.Key); } } else if (list.Count > 0) { //如果發現有向環,則拋出異常 throw new InvalidOperationException("存在雙向引用或循環引用。"); } } } } /// <summary> /// 拓撲節點類。 /// </summary> public class TopologicNode { /// <summary> /// 獲取或設置節點的鍵值。 /// </summary> public string Key { get; set; } /// <summary> /// 獲取或設置依賴節點的鍵值列表。 /// </summary> public List<string> Dependences { get; set; } } }
測試代碼如下:
using System; using System.Collections.Generic; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { List<TopologicNode> nodes = new List<TopologicNode>() { new TopologicNode(){ Key = "XMedia", Dependences = new List<string>(){ "XMedia.Controllers", "XMedia.Models", "XMedia.Logics", "XMedia.Commons" } }, new TopologicNode(){ Key = "XMedia.Controllers", Dependences = new List<string>(){"XMedia.Models","XMedia.Logics","XMedia.Commons"}}, new TopologicNode(){ Key = "XMedia.Logics", Dependences = new List<string>(){ "XMedia.Models","XMedia.Commons"}}, new TopologicNode(){ Key = "XMedia.Models" }, new TopologicNode(){ Key = "XMedia.Commons" } }; //輸出拓撲排序的結果 TopologicSort sort = new TopologicSort(); foreach (var key in sort.OrderBy(nodes)) { Console.WriteLine(key); } Console.ReadLine(); } } }
運行結果如下圖所示: