c#类图生成器

3 篇文章 0 订阅

原文:http://www.codeproject.com/Articles/834517/Class-Diagram-Generator?msg=4932808#xx4932808xx

作者是个老外,东西不是很难,基本都是反射,但是做出这样的工具,想法很好,便于开发人员快速了解dll文件内部结构,要给一个大大的赞!!!

下面是我测试的一个dll文件,且看生成的图片效果:


这仅仅是其中一张图,很明了吧。

原文内容摘录如下:

Introduction

This article targets class diagram generation and the in-ability of Visual Studio to export individual or all class diagrams as single files. We will look at Assemblies and how to get information like Fields, Properties, Methods and Events back from them. 

Download ClassDiagramGen_EXE.zip

Download source

Background

While all developers hate documentation and repetitive tasks it is still a part of our sometimes daily tasks.

Yes there are tools out there that ease the pain like Ghost Doc or Sandcastle Help Builder, but none of them actually generate class diagrams as well. The Code Project too houses a couple of class diagram generators but what makes this one different? Well... I'd say the ability to create a project with multiple entries pointing to dll's or exe that you wish to dump to class diagrams. This article will describe how I went about analyzing the dll or exe and how one then generates the class diagrams. Another feature of this code is to point it to the code directory and add remarks to the class headers; assisting with CHM generation that contains a visual representation of your class.

Using the code

Analyzing a .NET dll or exe.

[The following code is found in CDGenerator.cs]

        /// <summary>
        /// Analyzes the DLL.
        /// </summary>
        /// <param name="dllLocation">The DLL location.</param>
        /// <param name="excludeServices">if set to <c>true</c> [exclude services].</param>
        /// <param name="excludeCLR">if set to <c>true</c> [exclude color].</param>
        public static void AnalyzeDLL(string dllLocation, bool excludeServices = false, bool excludeCLR = true)
        {
            string assemblyNameSpace = "";
            Type currentType = null;
            Type[] typesFound = null;
            ClassList = new List<ClassInfo>();
            CDAssembly = null;

            try
            {
                if (dllLocation != string.Empty && File.Exists(dllLocation))
                {
                    CDAssembly = Assembly.LoadFrom(dllLocation);
                    if (CDAssembly.FullName != "")
                    {
                        assemblyNameSpace = CDAssembly.FullName.Substring(0, CDAssembly.FullName.IndexOf(","));

                        typesFound = CDAssembly.GetTypes();

                        if (typesFound != null)
                        {
                            foreach (var type in typesFound)
                            {
                                if (type.Namespace != null)
                                {
                                    if (type.IsNotPublic)
                                    {
                                        continue;
                                    }
                                    else
                                    {
                                        //Excludes Meta data classes and only generate for the current namespace
                                        if (!type.FullName.Contains("Metadata") && type.Namespace.Contains(assemblyNameSpace))
                                        {
                                            if (excludeServices && type.FullName.Contains("Service."))
                                            {
                                                continue;
                                            }

                                            //Exclude weird naming conventions.. usually generated classes not coded by a developer
                                            if (!type.FullName.Contains("<") && !type.FullName.Contains(">"))
                                            {
                                                currentType = type;
                                                ClassList.Add(new ClassInfo(Path.GetFileName(dllLocation), type, excludeCLR));
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(string.Format("{0}: {1}", currentType.Name, ex.Message));
                throw ex;
            }
        }

Have a look at the AnalyzeDLL method above. You will see a few helper classes used inside.
ClassInfo : Stores information like Class Type, Name and a List of BasicInfo found inside the dll or exe.
BasicInfo :  Stores the info type (e.g. Fields, Properties, Constructor, Methods or Events).

The code starts by setting a couple of variable and doing a couple of checks to ensure we actually passed it a valid location to the dll or exe to analyze.

The actual work of loading the dll or exe into memory is done by the following command.

CDAssembly = Assembly.LoadFrom(dllLocation);

I use Assembly.LoadFrom() to ensure that we don't get any errors with referenced assemblies that possibly lay in the same directory as the loaded file. This is generally the problem when loading a dll or exe with Assembly.Load().

To ensure that the file is actually loaded a check is done on the assemblies full name after wich the namespace is recorded into the assemblyNameSpace variable.

A array of types are extracted from the assembly by using the GetTypes() method on our CDAssembly.

Further checks are done to see that types are actually found and that they are publically accessible.
Meta data is excluded and a check is done that we only look at methods in the main Namespace.

If all these checks pass the current type is added to the ClassList where a new ClassInfo item is created.

ClassList.Add(new ClassInfo(Path.GetFileName(dllLocation), currentType, excludeCLR));

By looking closer at the ClassInfo constructor you will find that a check is done on the type of class (e.g. Class, Interface, AbstractClass, Enum or Struct).

Once we know what type the class is the GetMoreInfo() method is called. This method finds all the Properties, Fields, Constructors, Methods and Events.

All of these are found in the same fashion.

Let's have a look at how the Fields are found:

FieldInfo[] fieldInfos = referenceType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.CreateInstance);

An array is built up by checking the assembly type and calling the GetMethods() method. This holds true for Fields(GetFields()), Properties (GetProperties()) etc. Flags are set to Public , Static , Instance or CreateInstance.

if (methodInfos != null && methodInfos.Length > 0)
                {
                    foreach (MethodInfo methodInfo in methodInfos)
                    {
                        //Only add custom methods. Don't show built in DotNet methods
                        if (excludeCLR && methodInfo.Module.ScopeName != ReferenceFileName)
                        {
                            continue;
                        }
                        else
                        {
                            ClassInformation.Add(new BasicInfo(BasicInfo.BasicInfoType.Methods, methodInfo.Name, methodInfo.ReturnType));
                        }
                    }
                }

Once the array has been built up a check is done to check for nulls and that the array actually contains records.
When the check passes we loop through all the methods found whilst checking if the method falls within the scope of the file passed and if built in .NET fields must be excluded. An example of a built in .NET method would be ToString() or GetType().

Generating a Class Diagram


public static void GeneratesClassDiagram(ClassInfo classInfo, string outputPath, bool includeProperties = true, bool includeMethods = true, bool includeEvents = true, bool includeFields = true, bool includeConstructor = true)
        {... }
​

 Now that we have a Class (ClassInfo) with lists of all its Constructor, Fields, Properties,  Methods and Events we can actually generate an class diagram image.
 This is all done by the GeneratesClassDiagram() method contained in the CDGenerator   Class.

 A helper method and class is used inside GeneratesClassDiagram. 
 CalculateSize : Calculates the image size for the new class diagram based on how many  constructors, fields, properties, methods and events are present.

 RoundedRectangle :  This class creates a graphic path for GDI+ to draw. It aids with calculating  the rounded corners based on the given width, height and diameter. 

Graphics are drawn in 'Layers' to represent the class box and its shadow along with a gradient title containing the class name and type. Based on configuration settings each class type can have a different color.

AntiAliasing is applied to enhance text readability.

//Enable AntiAliasing
g.TextRenderingHint = TextRenderingHint.AntiAlias;

Finally when all the 'Layers' are drawn; a size comparison is done to avoid saving empty class diagrams.

Remarking the Code

Remarking the code is done by calling the RemarkAllImages() method.
It takes the code path and image path as variables and then proceeds to "Remark" all classes in code that have a resulting class diagram image.

/// <summary>
        /// Remarks all images.
        /// </summary>
        /// <param name="codePath">The code path.</param>
        /// <param name="imagesPath">The images path.</param>
        public static void RemarkAllImages(string codePath, string imagesPath)
        {
            string currentClassName = "";
            string remarkTemplate = Properties.Settings.Default.RemarkTemplate;
            string searchString = "";
            string currentClassRemark = "";

            int startIndex = 0;
            StreamReader sr;
            StreamWriter sw;
            FileInfo currentCodeFile;
            string currentCodeFileText = "";
            string outCodeFileText = "";
            if (Directory.Exists(imagesPath))
            {
                DirectoryInfo codeDirInfo = new DirectoryInfo(codePath);

                FileUtils.GetAllFilesInDir(codeDirInfo, "*.cs");

                foreach (string fileName in Directory.GetFiles(imagesPath, "*.png"))
                {
                    startIndex = 0;
                    try
                    {
                        currentClassName = Path.GetFileName(fileName).Replace(".png", "");
                        currentCodeFile = FileUtils.files.Where(f => f.Name == string.Format("{0}.cs", currentClassName)).FirstOrDefault();

                        if (currentCodeFile != null)
                        {
                            using (sr = new StreamReader(currentCodeFile.FullName))
                            {
                                currentCodeFileText = sr.ReadToEnd();
                                sr.Close();

                                //Finding the class description logic goes here. In essence it results in something like this:
                                //searchString = string.Format("public {0}", currentClassName);
                                

                                startIndex = currentCodeFileText.IndexOf(searchString);

                                //Add the remark
                                currentClassRemark = string.Format("{0}{1}\t", string.Format(remarkTemplate, currentClassName), Environment.NewLine);

                                if (!currentCodeFileText.Contains(currentClassRemark))
                                {
                                    outCodeFileText = currentCodeFileText.Insert(startIndex, currentClassRemark);

                                    using (sw = new StreamWriter(currentCodeFile.FullName, false))
                                    {
                                        sw.WriteLine(outCodeFileText);
                                        sw.Close();
                                    }
                                }

                                if (RemarkDone != null)
                                {
                                    RemarkDone(currentClassName);
                                }
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        ErrorOccurred(ex, null);
                    }
                }
            }
        }

Have a look at the RemarkAllImages method above. 

It makes use of the FileUtils class to find all C# code files:

FileUtils.GetAllFilesInDir(codeDirInfo, "*.cs");

The method has a loop that then gets all png files and with the help of LINQ it filters out all code files that do not have a resulting image.

FileUtils.files.Where(f => f.Name == string.Format("{0}.cs", currentClassName)).FirstOrDefault();

A bit of logic is applied to build up a search string based on the class type.
The search string then finds the class header and adds the remark above that using the remark template.

Once the string is appended it is written back to the code file.

Points of Interest

We already use this tool quite extensively at our place of work and it is in the process of being expanded to include EF helper class generation, SQL Database commenting from comments in code and database dictionary generation. Hopefully one-day I can upload it here as a tool that can assist an even greater audience.


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值