还在手画C#依赖关系图吗?快来试试这个工具吧!

还在手画C#依赖关系图吗?快来试试这个工具吧!

笔者最近见到了一个不错的工具,可以让大家在看代码的时候一键生成C#依赖的类图。非常适合编写文档、查看和学习开源项目设计时使用,比如下方就是笔者通过这个工具生成的Microsoft.Extensions.ObjectPool依赖图,可以非常清晰明了的告诉我们类与类之间的关系。

GITHUB地址:

https://github.com/pierre3/PlantUmlClassDiagramGenerator

4a1e4aef0f1aec0c1867f6962386bf6f.png
image-20221107232048503

介绍PlantUmlClassDiagramGenerator

这是一个生成器,用于从C#源代码中创建PlantUML的类图。

Visual Studio Code 扩展

  • C# to PlantUML[1]

aaa32791c4d573592d6e938ecf3e206c.png
image-20221107232754756

.Net Core 全局工具

Nuget Gallery: https://www.nuget.org/packages/PlantUmlClassDiagramGenerator

安装

下载并安装.NET 6.0 SDK[2]或更新的版本。安装后,运行以下命令。

dotnet tool install --global PlantUmlClassDiagramGenerator

使用

运行 "puml-gen" 命令.

puml-gen InputPath [OutputPath] [-dir] [-public | -ignore IgnoreAccessibilities] [-excludePaths ExcludePathList] [-createAssociation]
  • InputPath: (必须) 设置一个输入源文件或目录名称。

  • OutputPath: (可选) 设置一个输出文件或目录名称。如果省略此选项,plantuml文件将被输出到与输入文件相同的目录中。

  • -dir: (可选) 当InputPath和OutputPath为目录名时,指定。

  • -public: (可选)  如果指定,只输出公共可及性成员。

  • -ignore: (可选) 指定要忽略的成员的可访问性,用逗号分隔的列表。

  • -excludePaths: (可选) 指定排除的文件和目录。
    指定来自 "InputPath "的相对路径,用逗号分隔的列表。

  • -createAssociation: (可选) 从字段和属性的引用中创建对象关联。

  • -allInOne: (可选) 只有当-dir被设置时:将所有图表的输出复制到文件include.puml(这允许PlanUMLServer渲染)。

  • -attributeRequired: (可选) 当这个开关被启用时,只有类型声明中带有 "PlantUmlDiagramAttribute "的类型会被输出。

例子:

puml-gen C:\Source\App1\ClassA.cs -public
puml-gen C:\Source\App1 C:\PlantUml\App1 -dir -ignore Private,Protected -createAssociation -allInOne
puml-gen C:\Source\App1 C:\PlantUml\App1 -dir -excludePaths bin,obj,Properties

生成好以后,会在对应的目录看到有一些*.puml的文件,然后可以用对应的PlantUML工具打开。

笔者这里使用的是Visual Studio Code打开的PlantUML图,需要安装一个插件,可能某些电脑需要安装Java环境。

8bfd2d3b8591518c11b93edb5e237911.png
image-20221107232315656

打开以后按Alt+D,或者右击 -> 预览光标位置图表就可以了,当然右击也可以导出图片。

1b63cc897e01fe2347c8b0e6cee5aa82.png
image-20221107232533751

下文是puml-gen工具对PlantUML的一些转换规则,大家有兴趣的可以了解一下。

转换为PlantUML的规范

类型声明

Type 关键字

C#PlantUML
classclass
struct<<struct>> class
interfaceinterface
enumenum
record<<record>> class

Type 修饰符

C#PlantUML
abstractabstract
static<<static>>
partial<<partial>>
sealed<<sealed>>
  • C#

class ClassA {  
}
struct StructA {
}
interface InterfaceA {
}
record RecordA {
}
abstract class AbstractClass {
}
static class StaticClass {
}
sealed partial class ClassB{
}
enum EnumType{Apple,Orange,Grape
}
  • PlantUML

@startuml
class ClassA {
}
class StructA <<struct>> {
}
interface InterfaceA {
}
class RecordA <<record>> {
}
abstract class AbstractClass {
}
class StaticClass <<static>> {
}
class ClassB <<sealed>> <<partial>> {
}
enum EnumType {Apple,Orange,Grape,
}
@enduml
e2802776fa57242512fcb3e69346da53.png
TypeDeclaration.png

泛型

  • C#

class GenericsType<T1>{
}
class GenericsType<T1,T2>{
}
  • PlantUML

class "GenericsType`1"<T1>{
}
class "GenericsType`2"<T1,T2>{
}
6bc0fefe426731349ec4f4bdcc008545.png
GenericsTypeDeclaration.png

成员声明

可见性修饰符

C#PlantUML
public+
internal<<internal>>
protected internal# <<internal>>
protected#
private-

修饰符

C#PlantUML
abstract{abstract}
static{static}
virtual<<virtual>>
override<<override>>
new<<new>>
readonly<<readonly>>
event<<event>>

属性访问器

C#PlantUML
int Prop {get; set;}Prop : int <<get>> <<set>>
int Prop {get;}Prop : int <get>
int Prop {get; private set }Prop : int <<get>><<private set>>
int Prop => 100;Prop : int <<get>>
  • C#

abstract class AbstractClass
{protected int _x;internal int _y;protected internal int _z;public abstract void AbstractMethod();protected virtual void VirtualMethod(string s){}public string BaseMethod(int n){return "";}
}
class ClassM : AbstractClass
{public static readonly double PI =3.141592;public int PropA { get; set; }public int PropB { get; protected set; }public event EventHandler SomeEvent;public override void AbstractMethod(){}protected override void VirtualMethod(string s){}public override string ToString(){return "override";}public new string BaseMethod(int n){return "new";}
}
  • PlantUML

abstract class AbstractClass {# _x : int<<internal>> _y : int# <<internal>> _z : int+ {abstract} AbstractMethod() : void# <<virtual>> VirtualMethod(s:string) : void+ BaseMethod(n:int) : string
}
class ClassM {+ {static} <<readonly>> PI : double = 3.141592+ PropA : int <<get>> <<set>>+ PropB : int <<get>> <<protected set>>+  <<event>> SomeEvent : EventHandler + <<override>> AbstractMethod() : void# <<override>> VirtualMethod(s:string) : void+ <<override>> ToString() : string+ <<new>> BaseMethod(n:int) : string
}
AbstractClass <|-- ClassM
14edb26d3e34072fe482fe4f4b4d67f1.png
MemberDeclaration.png

字段和属性初始化

只有常量的初始化才会被输出。

  • C#

class ClassC
{private int fieldA = 123;public double Pi {get;} = 3.14159;protected List<string> Items = new List<string>(); 
}
  • PlantUML

class ClassC {- fieldA : int = 123+ Pi : double = 3.14159# Items : List<string>
}
0db9704b49fb90ae862f7248bed6bde2.png
Initializer.png

嵌套类声明

嵌套类被展开并与 "OuterClass + - InnerClass "关联。

  • C#

class OuterClass 
{class InnerClass {struct InnerStruct {}}
}
  • PlantUML

class OuterClass{}
class InnerClass{}
<<struct>> class InnerStruct {}
OuterClass +- InnerClass
InnerClass +- InnerStruct
190785d8a81c9b408ab03df603c34692.png
NestedClass.png

继承关系

  • C#

abstract class BaseClass
{public abstract void AbstractMethod();protected virtual int VirtualMethod(string s) => 0;
}
class SubClass : BaseClass
{public override void AbstractMethod() { }protected override int VirtualMethod(string s) => 1;
}interface IInterfaceA {}
interface IInterfaceA<T>:IInterfaceA
{T Value { get; }
}
class ImplementClass : IInterfaceA<int>
{public int Value { get; }
}
  • PlantUML

abstract class BaseClass {+ {abstract} AbstractMethod() : void# <<virtual>> VirtualMethod(s:string) : int
}
class SubClass {+ <<override>> AbstractMethod() : void# <<override>> VirtualMethod(s:string) : int
}
interface IInterfaceA {
}
interface "IInterfaceA`1"<T> {Value : T <<get>>
}
class ImplementClass {+ Value : int <<get>>
}
BaseClass <|-- SubClass
IInterfaceA <|-- "IInterfaceA`1"
"IInterfaceA`1" "<int>" <|-- ImplementClass
825b269e0801e7b50b3e20ed3464fb64.png
InheritanceRelationsips.png

关联(来自字段和属性的引用)

如果你指定了 "createAssociation "选项,对象关联将从字段和属性引用中创建。

  • C#

class ClassA{public IList<string> Strings{get;} = new List<string>();public Type1 Prop1{get;set;}public Type2 field1;
}class Type1 {public int value1{get;set;}
}class Type2{public string string1{get;set;}public ExternalType Prop2 {get;set;}
}
  • PlantUML

@startuml
class ClassA {
}
class Type1 {+ value1 : int <<get>> <<set>>
}
class Type2 {+ string1 : string <<get>> <<set>>
}
class "IList`1"<T> {
}
ClassA o-> "Strings<string>" "IList`1"
ClassA --> "Prop1" Type1
ClassA --> "field1" Type2
Type2 --> "Prop2" ExternalType
@enduml
0934fbff2ce0aad53dfeb02aa590d728.png
InheritanceRelationsips.png

记录类型(含参数列表)

C# 9中的记录类型可以有一个参数列表。在这些情况下,这些参数 被作为属性添加到类中。

  • C#

record Person(string Name, int Age);record Group(string GroupName) {public Person[] Members { get; init; }
}
  • PlantUML

@startuml
class Person <<record>> {+ Name : string <<get>> <<init>>+ Age : int <<get>> <<init>>
}
class Group <<record>> {+ GroupName : string <<get>> <<init>>+ Members : Person[] <<get>> <<init>>
}
@enduml
58894db4c2b18d5c311cc0ea88796b7f.png
InheritanceRelationsips.png

基于特性的配置

你可以将PlantUmlClassDiagramGenerator.Attributes[3]包添加到你的C#项目中,用于基于特性的配置。

PlantUmlDiagramAttribute

只有被添加了PlantUmlDiagramAttribute的类型才会被输出。如果-attributeRequired开关被添加到命令行参数中,这个属性就会被启用。

这个属性只能被添加到类型声明中。

  • class

  • struct

  • enum

  • record

class ClassA
{public string Name { get; set; }public int Age { get; set; }
}[PlantUmlDiagram]
class ClassB
{public string Name { get; set; }public int Age { get; set; }
}

只有带有PlantUmlDiagramAttribute的ClassB会被输出。

@startuml
class ClassB {+ Name : string <<get>> <<set>>+ Age : int <<get>> <<set>>
}
@enduml

PlantUmlIgnoreAttribute

添加了这个属性的元素被排除在输出之外。

[PlantUmlIgnore]
class ClassA
{public string Name { get; set; }public int Age { get; set; }
}class ClassB
{public string Name { get; set; }[PlantUmlIgnore]public int Age { get; set; }
}class ClassC
{public string Name { get; set; }public int Age { get; set; }[PlantUmlIgnore]public ClassC(string name, int age) => (Name, Age) = (name, age);public void MethodA();[PlantUmlIgnore]public void MethodB();
}
@startuml
class ClassB {+ Name : string
}
class ClassC {+ Name : string+ Age : int+ MethodA() : void
}
@enduml

PlantUmlAssociationAttribute

通过添加这个属性,你可以定义类之间的关联。这个属性可以被添加到属性、字段和方法参数。

关联的细节被定义在以下属性中。

  • Name

    • 指定叶子节点一侧的类型名称。

    • 如果省略,则使用添加该属性的元素的名称。

  • Association

    • 指定关联的边缘部分。在PlantUML中设置一个有效的字符串。

    • 如果省略,则使用"--"。

  • RootLabel

    • 指定显示在根节点一侧的标签。

    • 如果省略,则不显示。

  • Label

    • 指定要显示在边缘中心的标签。

    • 如果省略,则不显示。

  • LeafLabel

    • 指定显示在叶子节点一侧的标签。

    • 如果省略,则不显示。

class Parameters
{public string A { get; set; }public string B { get; set; }
}class CustomAssociationSample
{[PlantUmlAssociation(Name = "Name", Association = "*-->", LeafLabel = "LeafLabel", Label= "Label", RootLabel = "RootLabel")] public ClassA A { get; set; }
}class CollectionItemsSample
{[PlantUmlAssociation(Name = "Item", Association = "o--", LeafLabel = "0..*", Label = "Items")]public IList<Item> Items { get; set; }
}class MethodParamtersSample
{public void Run([PlantUmlAssociation(Association = "..>", Label = "use")] Parameters p){Console.WriteLine($"{p.A},{p.B}");}private ILogger logger;public MyClass([PlantUmlAssociation(Association = "..>", Label = "Injection")] ILogger logger){this.logger = logger;}
}
@startuml
class Parameters {+ A : string <<get>> <<set>>+ B : string <<get>> <<set>>
}
class CustomAssociationSample {
}
class CollectionItemsSample {
}
class MethodParamtersSample {+ Run(p:Parameters) : void+ MyClass(logger:ILogger)
}
CustomAssociationSample "RootLabel" *--> "LeafLabel" Name : "Label"
CollectionItemsSample o-- "0..*" Item : "Items"
MethodParamtersSample ..> Parameters : "use"
MethodParamtersSample ..> ILogger : "Injection"
@enduml
2690228fb64179c97c9ef0ed65e09797.png
CustomAssociation.png

PlantUmlIgnoreAssociationAttribute

这个属性可以被添加到属性和字段中。具有此属性的属性(或字段)被描述为类的成员,没有任何关联。

class User
{public string Name { get; set; }public int Age { get; set; }
}class ClassA
{public static User DefaultUser { get; }public IList<User> Users { get; }public ClassA(IList<User> users){Users = users;DefaultUser = new User(){Name = "DefaultUser",Age = "20"};}
}class ClassB
{[PlantUmlIgnoreAssociation]public static User DefaultUser { get; }[PlantUmlIgnoreAssociation]public IList<User> Users { get; }public ClassB(IList<User> users){Users = users;DefaultUser = new User(){Name = "DefaultUser",Age = "20"};}
}
@startuml
class User {+ Name : string <<get>> <<set>>+ Age : int <<get>> <<set>>
}
class ClassA {+ ClassA(users:IList<User>)
}
class ClassB {+ {static} DefaultUser : User <<get>>+ Users : IList<User> <<get>>+ ClassB(users:IList<User>)
}
class "IList`1"<T> {
}
ClassA --> "DefaultUser" User
ClassA --> "Users<User>" "IList`1"
@enduml
36b96e2920954fdbcd465cc7eb31601f.png
IgnoreAssociation.png

参考资料

[1]

C# to PlantUML: https://marketplace.visualstudio.com/items?itemName=pierre3.csharp-to-plantuml

[2]

.NET 6.0 SDK: https://www.microsoft.com/net/download/windows

[3]

PlantUmlClassDiagramGenerator.Attributes: https://www.nuget.org/packages/PlantUmlClassDiagramGenerator.Attributes

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/281255.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

51 Nod 1027 大数乘法【Java大数乱搞】

1027 大数乘法 基准时间限制&#xff1a;1 秒 空间限制&#xff1a;131072 KB 分值: 0 难度&#xff1a;基础题 给出2个大整数A,B&#xff0c;计算A*B的结果。Input第1行&#xff1a;大数A 第2行&#xff1a;大数B (A,B的长度 < 1000&#xff0c;A,B > 0&#xff09; Out…

关于ASP.NET Core WebSocket实现集群的思考

前言提到WebSocket相信大家都听说过&#xff0c;它的初衷是为了解决客户端浏览器与服务端进行双向通信&#xff0c;是在单个TCP连接上进行全双工通讯的协议。在没有WebSocket之前只能通过浏览器到服务端的请求应答模式比如轮询&#xff0c;来实现服务端的变更响应到客户端&…

更快,更强的.NET 7 发布了

.NET Conf 2022 在昨晚(11⽉8⽇) 11 点 正式开始了&#xff0c;为期三天的会议&#xff08;11⽉8-10⽇&#xff09;&#xff0c; 围绕 .NET 7 展开。相信各位⼩伙伴都已经开始安装 .NET 7 正式版本还有以及相关的开发⼯具。这次 .NET 7 围绕传统的 C# &#xff0c;ASP.NET Core…

jvm(Java virtual machine) JVM架构解释

2019独角兽企业重金招聘Python工程师标准>>> JVM 架构解释 每个Java开发者都知道通过JRE【Java运行环境】执行字节码。 但是很多人都不知道JRE是JVM实现的事实。JVM负责执行字节码的分析 代码的解释和运行。 我们应该了解JVM的架构&#xff0c;这对开发者来说是很重…

WinForm(十五)窗体间通信

在很多WinForm的程序中&#xff0c;会有客户端之间相互通信的需求&#xff0c;或服务端与客户端通信的需求&#xff0c;这时就要用到TCP/IP的功能。在.NET中&#xff0c;主要是通过Socket来完成的&#xff0c;下面的例子是通过一个TcpListerner作为监听&#xff0c;等待TcpClie…

关于Java开发需要注意的十二点流程

1.将一些需要变动的配置写在属性文件中 比如&#xff0c;没有把一些需要并发执行时使用的线程数设置成可在属性文件中配置。那么你的程序无论在DEV环境中&#xff0c;还是TEST环境中&#xff0c;都可以顺畅无阻地运行&#xff0c;但是一旦部署在PROD上&#xff0c;把它作为多线…

Unity经典游戏教程之:雪人兄弟

版权声明&#xff1a; 本文原创发布于博客园"优梦创客"的博客空间&#xff08;网址&#xff1a;http://www.cnblogs.com/raymondking123/&#xff09;以及微信公众号"优梦创客"&#xff08;微信号&#xff1a;unitymaker&#xff09;您可以自由转载&#x…

一款自用的翻译小工具,开源了

一款自用的翻译小工具&#xff0c;开源了TranslationTool作者&#xff1a;WPFDevelopersOrg - 唐宋元明清|驚鏵原文链接&#xff1a;https://github.com/Kybs0/TranslationTool此项目使用WPF MVVM开发。框架使用大于等于.NET461。Visual Studio 2019。最初是支持以下&#xff1…

【ELK集群+MQ】通用部署方案以及快速实现MQ发布订阅服务功能

前言&#xff1a;大概一年多前写过一个部署ELK系列的博客文章&#xff0c;前不久刚好在部署一个ELK的解决方案&#xff0c;我顺便就把一些基础的部分拎出来&#xff0c;再整合成一期文章。大概内容包括&#xff1a;搭建ELK集群&#xff0c;以及写一个简单的MQ服务。如果需要看一…

多语言报表的改动方法

在定义上传RTF模板的时候&#xff0c;会有一个是否可翻译的选项&#xff0c;选择之后。就能够上传xlf文件作为翻译内容。 对于已经存在的多语言类型报表&#xff0c;稍作改动之后再上传&#xff0c;可能会出现下面现象&#xff1a; 进程出现了“未完毕”的提示 想要改动非常eas…

LightOJ - 1027 A Dangerous Maze —— 期望

题目链接&#xff1a;https://vjudge.net/problem/LightOJ-1027 1027 - A Dangerous MazePDF (English)StatisticsForumTime Limit: 2 second(s)Memory Limit: 32 MBYou are in a maze; seeing n doors in front of you in beginning. You can choose any door you like. The p…

MASA MAUI Plugin (六)集成个推,实现本地消息推送[Android] 篇

背景MAUI的出现&#xff0c;赋予了广大.Net开发者开发多平台应用的能力&#xff0c;MAUI 是Xamarin.Forms演变而来&#xff0c;但是相比Xamarin性能更好&#xff0c;可扩展性更强&#xff0c;结构更简单。但是MAUI对于平台相关的实现并不完整。所以MASA团队开展了一个实验性项目…

微软加更.NET7中文手册,都有哪些新亮点?

11月8号发布了.NET7&#xff0c;从底层性能改进&#xff0c;到上层API升级&#xff0c;让.NET7综合性能再度提升&#xff01;同时发布了最新的C#11&#xff0c;也带来了很多小惊喜。如何快捷学习最新的.NET7和C#11&#xff1f;答案只有一个&#xff0c;微软官方中文文档&#x…

.NET Conf China 2022 第一批讲师阵容大揭秘!整个期待了!

目光看过来2022年12月3-4日一场社区性质的国内规模最大的线上线下.NET Conf 2022技术大会即将盛大开幕目前大会正紧锣密鼓地进行中第一批大咖讲师及主题已确定小编迫不及待想和大家分享分享嘉宾很大咖分享内容很硬核戳戳小手期待ing孔令磊维宏股份 首席架构师 十多年数控领域研…

妙用SQL Server聚合函数和子查询迭代求和

先看看下面的表和其中的数据&#xff1a;t_product该表有两个字段&#xff1a;xh和price&#xff0c; 其中xh是主索引字段&#xff0c;现在要得到如下的查询结果&#xff1a;从上面的查询结果可以看出&#xff0c;totalprice字段值的规则是从第1条记录到当前记录的price之和。如…

记一次.NET某工控图片上传CPU爆高分析

一&#xff1a;背景 1.讲故事今天给大家带来一个入门级的 CPU 爆高案例&#xff0c;前段时间有位朋友找到我&#xff0c;说他的程序间歇性的 CPU 爆高&#xff0c;不知道是啥情况&#xff0c;让我帮忙看下&#xff0c;既然找到我&#xff0c;那就用 WinDbg 看一下。二&#xff…

从 WinDbg 角度理解 .NET7 的AOT玩法

一&#xff1a;背景 1.讲故事前几天 B 站上有位朋友让我从高级调试的角度来解读下 .NET7 新出来的 AOT&#xff0c;毕竟这东西是新的&#xff0c;所以这一篇我就简单摸索一下。二&#xff1a;AOT 的几个问题 1. 如何在 .NET7 中开启 AOT 功能在 .NET7 中开启 AOT 非常方便&…

【PPT】适配器模式 和 桥接模式

【PPT】适配器模式 和 桥接模式目录【PPT】适配器模式 和 桥接模式一、PPT 截图1.0、封面和目录1.1、设计模式概述1.2、结构型模式特点1.3、适配器模式1.4、桥接模式二、参考资料及 PPT 获取方法独立观察员 2022 年 11 月 15 日为之前公司准备的分享PPT&#xff0c;后来没用上。…

Flask 【第七篇】Flask中的wtforms使用

一、简单介绍flask中的wtforms WTForms是一个支持多个web框架的form组件&#xff0c;主要用于对用户请求数据进行验证。 安装&#xff1a; pip3 install wtforms 二、简单使用wtforms组件 1、用户登录 具体代码&#xff1a; from flask import Flask,render_template,request,…

为了避免内存攻击,美国国家安全局提倡Rust、C#、Go、Java、Ruby 和 Swift,但将 C 和 C++ 置于一边...

本文翻译自两篇文章&#xff0c;第一篇是对美国国家安全局在“软件内存安全”网络安全信息表的解读&#xff0c;第二篇是普及什么是内存安全&#xff0c;为什么它很重要&#xff1f;第一篇 为了避免内存攻击&#xff0c;美国国家安全局提倡Rust、C#、Go、Java、Ruby 和 Swift&a…