在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),而项目的编译顺序就是一个拓扑序列,产生该序列的算法称为拓扑排序算法。

以下是项目引用关系的有向图展示:

Java自动计算拓扑图坐标_List

拓扑排序算法的简要描述:
(1) 从有向图中选择一个出度为0的顶点并且输出它。
(2) 从图中删去该顶点,并且删去该顶点的所有边。
(3) 重复上述两步,直到剩余的图中没有出度为0的顶点。

按照上述算法,运行过程演示如下:

第一步 选择 XMedia.Commons节点

Java自动计算拓扑图坐标_测试_02

第二步 选择XMedia.Models节点

Java自动计算拓扑图坐标_List_03

第三步 选择XMedia.Logics节点

Java自动计算拓扑图坐标_Java自动计算拓扑图坐标_04

第四步 选择XMedia.Controllers节点

Java自动计算拓扑图坐标_数据结构与算法_05

第五步 选择XMedia节点

Java自动计算拓扑图坐标_测试_06

 

接下来我们用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();
        }
    }
}



运行结果如下图所示:

Java自动计算拓扑图坐标_List_07