LoginSignup
18
13

C#のソースコードからPlantUmlのクラス図を生成するツールを正式リリースしました

Posted at

PlantUmlClassDiagramGenerator.SourceGenerator 1.0.0

C#のソースコードからPlantUmlのクラス図を生成するツールPlantUmlClassDiagramGenerator.SourceGenerator 1.0.0をリリースしました。
最低限必要な機能が実装できたところでPreviewを外し正式版としましたが、引き続きフィードバックをお待ちしております。

ソースコード

0.6.0-beta からの変更点

自動的に付与される関連付けの作成を、その種類ごとに無効化する機能を追加しました。

リリースノート

  • PlantUmlDiagramAttributeDisableAssociationTypesプロパティを追加
  • PlantUmlDiagramAttributeIncludeMemberAccessibilitiesプロパティおよびExcludeMemberAccessibilitiesプロパティ設定時の挙動を修正

使い方

1. NuGet パッケージのインストール

PlantUmlClassDiagramGenerator.SourceGenerator パッケージを NuGet Gallery から取得し、.NET プロジェクトにインストールします。

2. プロジェクトファイルの編集

2.1 条件付きコンパイルシンボルに "GENERATE_PLANTUML" を含める

このツールは、プリプロセッサシンボルに "GENERATE_PLANTUML" が定義されている場合にのみ動作します。ツールはコーディング中に常に動作する必要がなく、必要になったタイミングで 1 度だけ実行すれば十分です。そのため、特定のビルド構成時のみ動作する仕組みとしています。

プロジェクトのビルド構成の条件付きコンパイルシンボルに "GENERATE_PLANTUML" を追加します。

リリースビルド時にツールを実行するように設定するには、.csproj ファイルに以下のセクションを追加します。

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
    <DefineConstants>$(DefineConstants);GENERATE_PLANTUML</DefineConstants>
</PropertyGroup>

2.2 プロジェクト設定の追加

プロジェクトファイル(.csproj)に下記セクションを追加します。

<ItemGroup>
	<CompilerVisibleProperty Include="PlantUmlGenerator_OutputDir" />
</ItemGroup>
<PropertyGroup>
	<PlantUmlGenerator_OutputDir>$(ProjectDir)generated-uml</PlantUmlGenerator_OutputDir>
</PropertyGroup>
プロパティ 説明 既定値
PlantUmlGenerator_OutputDir 生成したUMLファイルの配置先ディレクトリを指定します。
異なるプロジェクト間で関連付けを行いたい場合は、関連する全てのプロジェクトで同じディレクトリを指定してください。
$(ProjectDir)generated_uml

3. 属性値の設定

必要に応じて、型やそのメンバーに対して属性値を設定します。

PlantUmlDiagramAttribute

アセンブリと型の定義(クラス、構造体、インターフェイス、列挙型)に付与することができます。
この属性を付与した型がクラス図の出力対象となります。
アセンブリに付与した場合はアセンブリ内で定義している全ての型を出力の対象とします。

[assembly:PlantUmlDiagram]
[PlantUmlDiagram]
class ClassA
{

}

プロパティ

PlantUmlDiagramAttributeには次のプロパティが指定可能です。

プロパティ 概要
IncludeMemberAccessibilities Accessibilities 列挙型 クラス図に含めるメンバーのアクセシビリティを指定します
ExcludeMemberAccessibilities Accessibilities 列挙型 クラス図から除外するメンバーのアクセシビリティを指定します
DisableAssociationTypes AssociationTypes 列挙型 関連付けの対象から除外する関連付け種別を指定します
[Flags]
internal enum Accessibilities
{
    NotSet = 0x8000,
    None = 0,
    Public = 0x01,
    Protected = 0x02,
    Internal = 0x04,
    ProtectedInternal = 0x08,
    PrivateProtected = 0x10,
    Private = 0x20,
    All = Public | Protected | Internal | ProtectedInternal | PrivateProtected | Private
}
[Flags]
internal enum AssociationTypes
{
    NotSet = 0x8000,
    None = 0,
    Inheritance = 0x01,
    Realization = 0x02,
    Property = 0x04,
    Field = 0x08,
    MethodParameter = 0x10,
    Nest = 0x20,
    All = Inheritance | Realization | Property | Field | MethodParameter | Nest
}
IncludeMemberAccessibilitiesプロパティ

クラス図に含めるメンバーのアクセシビリティを指定します。

  • Noneを指定した場合、全てのメンバーが出力対象外となります
  • Allを指定すると全てのメンバーが出力対象となります
  • 未指定(NotSet)の場合はAllと同じく全てのメンバーが出力対象となります
  • アセンブリと個別の型定義の両方に指定がある場合は、型定義の設定が優先されます
[assembly:PlantUmlDiagram(
    IncludeMemberAccessibilities = Accessibilities.All)]

[PlantUmlDiagram(IncludeMemberAccessibilities = Accessibilities.Public
    | Accessibilities.Protected)]
class ClassA
{
    private int n = 0;
    public int X { get;set;}
    protected string A { get;set; }
}
ExcludeMemberAccessibilitiesプロパティ

クラス図から除外するアクセシビリティを指定します。ここで指定したアクセシビリティはIncludeMemberAccessibilitiesの設定に関わらず出力の対象外となります。

  • Noneを指定した場合はIncludeMemberAccessibilitiesで指定したアクセシビリティがそのまま出力対象となります
  • Allを指定した場合、全てのメンバーが出力の対象外となります
  • 未指定(NotSet)の場合はNoneを指定した場合と同じ結果になります
  • アセンブリと個別の型定義の両方に指定がある場合は、型定義の設定が優先されます
[assembly:PlantUmlDiagram(
    IncludeMemberAccessibilities = Accessibilities.Protected 
    | IncludeMemberAccessibilities = Accessibilities.Private,
)]

[PlantUmlDiagram(ExcludeMemberAccessibilities = Accessibilities.Private)]
class ClassA
{
    private int n = 0;
    public int X { get;set;}
    protected string A { get;set; }
}
DisableAssociationTypesプロパティ

関連付けの対象から除外する関連付け種別を指定します。

  • Noneを指定した場合は除外対象なしとして、自動で付与される全ての関連付けが有効となります
  • Allを指定した場合、自動で付与される全ての関連付けを無効化します
  • 未指定(NotSet)の場合はNoneを指定した場合と同じ結果になります
  • アセンブリと個別の型定義の両方に指定がある場合は、型定義の設定が優先されます
[PlantUmlDiagram]
public record Item(string Name, double Value);

[PlantUmlDiagram] 
interface IItemProvider
{
    Item Item { get; }
}

[PlantUmlDiagram]
class ItemProviderA : IItemProvider
{
    private Item _item;
    public Item Item { get; }
    public Item(Item item)
    {
        _item = item;
    }
}

[PlantUmlDiagram(DisableAssociationTypes = AssociationTypes.Field 
    | DisableAssociationTypes.Realization)]
class ItemProviderB : IItemProvider
{
    private Item _item;
    public Item Item { get; }
    public Item(Item item)
    {
        _item = item;
    }
}

PlantUmlIgnoreAttribute

アセンブリ単位でPlantUmlDiagramAttributeを定義している場合、アセンブリ内で定義されている全ての型が出力の対象となります。
一部の型を出力の対象外としたい場合は、その型にPlantUmlIgnoreAttributeを設定します。

[assembly:PlantUmlDiagram]

[PlantUmlIgnore]
class ClassA
{
}

class ClassB
{

}

PlantUmlIgnoreAttributeはある型の特定のメンバーのみを非表示としたい場合にも利用できます。

[PlantUmlDiagram]
class ClassA
{
    public int X {get;set;}
    [PlantUmlIgnore]
    public int Y {get;set;}

    public void MethodA(){}
    [PlantUmlIgnore]
    public void MethodB(){}
}

PlantUmlAssociationAttribute

この属性は型のメンバーやメソッドのパラメータに付与し、独自の関連付けを作成する場合に使用します。
下記プロパティで作成する関連の詳細を指定します。ここでは属性が付与されている型をRoot Type、関連する相手方の型をLeaf Type とします。

プロパティ 説明
Node string 関連の種類に対応した文字列を指定します。(o--..>など)
LeafType System.Type Leaf側の型を指定
RootLabel string Root側に付加するラベルを指定
NodeLabel string RootとLeafを結ぶ線上に付加するラベルを指定
LeafLabel string Leaf側に付加するラベルを指定
internal class SampleModel
{
    private readonly ILogger logger;

    [PlantUmlAssociation("*--",
        LeafType = typeof(Item),
        RootLabel = "IDictionary<string,Item>",
        LeafLabel = "*",
        NodeLabel = nameof(Items))]
    public IDictionary<string, Item> Items { get; } = new Dictionary<string, Item>();

    public SampleModel([PlantUmlAssociation("..>", NodeLabel = "Injection")] ILogger logger)
    {
        this.logger = logger;
    }
}
@startuml SampleModel
class SampleModel {
    - <<readonly>> logger : ILogger
    + <<readonly>> Items : IDictionary<string, Item> <<get>>
    + SampleModel(logger : ILogger)
}
SampleModel o-l- ILogger : logger
SampleModel "IDictionary<string,Item>" *-- "*" Item : Items
SampleModel ..> ILogger : Injection
@enduml

PlantUmlIgnoreAssociationAttribute

関連を作成したくないメンバーに付与することで、自動的な関連の生成を抑制します。

internal class SampleModel
{
    [PlantUmlIgnoreAssociation]
    private readonly ILogger logger;

    [PlantUmlAssociation("*--",
        LeafType = typeof(Item),
        RootLabel = "IDictionary<string,Item>",
        LeafLabel = "*",
        NodeLabel = nameof(Items))]
    public IDictionary<string, Item> Items { get; } = new Dictionary<string, Item>();

    public SampleModel([PlantUmlIgnoreAssociation] ILogger logger)
    {
        this.logger = logger;
    }
}
@startuml SampleModel
class SampleModel  {
    - <<readonly>> logger : ILogger
    + <<readonly>> Items : IDictionary<string, Item> <<get>>
    + SampleModel(logger : ILogger)
}
SampleModel "IDictionary<string,Item>" *-- "*" Item : Items
@enduml

PlantUmlExtraAssociationTargetsAttribute

関連付けの対象とする追加の型を指定します。

このツールでは、次の条件に当てはまる型を関連付けの対象としています。

  • プロジェクト内で出力対象となっている型
  • 出力先フォルダ内に.pumlファイルが存在する型

これ以外の型との関連を作成したい場合は PlantUmlExtraAssociationTargetsAttribute で登録します。
この属性はアセンブリと型定義に付与できます。

[assembly: PlantUmlExtraAssociationTargets(
    typeof(KeyValuePair<,>),
    typeof(System.Net.Http.HttpClient))]

[PlantUmlExtraAssociationTargets(typeof(System.IO.Textwriter))]
internal class SampleModel
{
    private HttpClient httpClient;
    public IDictionary<string, Item> Items { get; set; } = new Dictionary<string, Item>();
    
    public SampleModel(HttpClient httpClient)
    {
        this.httpClient = httpClient;
    }

    public void Write(TextWriter writer)
    {
        writer.Write("hoge");
    }
}
@startuml SampleModel
class SampleModel  {
    - httpClient : HttpClient
    + Items : IDictionary<string, Item> <<get>> <<set>>
    + SampleModel(httpClient : HttpClient)
    + Write(writer : TextWriter) : void
}
SampleModel o-- HttpClient : httpClient
SampleModel *-- "*" "KeyValuePair`2" : Items
SampleModel ..> HttpClient
SampleModel ..> TextWriter
@enduml

仕様

UMLファイルの出力

PlantUmlGenerator_OutputDirで指定した出力先ディレクトリ配下に「アセンブリ名」のフォルダ、その下に「名前空間」のフォルダが作成されます。

例:
以下のプロジェクト構成のソリューションがあるとします。

プロジェクト アセンブリ名 名前空間
Consoto.App1.csproj Consoto.App1 ・Consoto.App1
・Consoto.App1.ViewModel
・Consoto.App1.Model
Consoto.App1.Core.csproj Consoto.App1.Core ・Consoto.App1.Core
・Consoto.App1.Core.Extensions

各プロジェクト毎にPlantUmlClassDiagramGenerator.SourceGeneratorのパッケージをインストールし、PlantUmlGenerator_OutputDir$(SolutionDir)generated_umlとした場合、下図のようなフォルダ構成でUMLファイルが出力されます。

型の表現

PlantUMLで利用できる型のキーワードは以下の通りです。

  • class
  • struct
  • interface
  • abstract class
  • enum

record, static,sealedなどの修飾子はステレオタイプ(<<keyword>>)で表現します。

C#
class ClassA
{
    public string Name { get; }
    public int Value { get; }
    public ClassA(string name, int value) => (Name, Value) = (name, value);
}

static class StaticClass
{
    public static string SpecificName = "Hoge";
    public static string Piyo(int count) => string.Join(" ", Enumerable.Repeat("Pyyo", count));
}

abstract class AbstractClass
{
    public abstract void MethodA();
    public abstract void MethodB();
}

record RecordA(string Name,int Value);

public struct StructA()
{
    public float X { get; set; }
    public float Y { get; set; }
    public float Z { get; set; }
}

public record struct RecordStruct(float X, float Y, float Z);

public enum LogLevel
{
    Trace,
    Debug,
    Info,
    Warn,
    Error,
    Fatal
}

[Flags]
enum Accessibilities
{
    None = 0,
    Public = 0x01,
    Protected = 0x02,
    Internal = 0x04,
    ProtectedInternal = 0x08,
    PrivateProtected = 0x10,
    Private = 0x20,
    All = Public | Protected | Internal | ProtectedInternal | PrivateProtected | Private
}
PlantUml
class ClassA  {
    + <<readonly>> Name : string <<get>>
    + <<readonly>> Value : int <<get>>
    + ClassA(name : string, value : int)
}
class StaticClass <<static>>  {
    + {static} SpecificName : string
    + {static} Piyo(count : int) : string
    - {static} StaticClass()
}
abstract class AbstractClass  {
    + {abstract} MethodA() : void
    + {abstract} MethodB() : void
    # AbstractClass()
}
class RecordA <<record>>  {
    + RecordA(Name : string, Value : int)
    # <<readonly>> <<virtual>> EqualityContract : Type <<get>>
    + Name : string <<get>> <<set>>
    + Value : int <<get>> <<set>>
    + <<override>> ToString() : string
    # <<virtual>> PrintMembers(builder : StringBuilder) : bool
    + {static} operator !=(left : RecordA?, right : RecordA?) : bool
    + {static} operator ==(left : RecordA?, right : RecordA?) : bool
    + <<override>> GetHashCode() : int
    + <<override>> Equals(obj : object?) : bool
    + <<virtual>> Equals(other : RecordA?) : bool
    # RecordA(original : RecordA)
    + Deconstruct(Name : string, Value : int) : void
}
struct StructA <<sealed>>  {
    + StructA()
    + X : float <<get>> <<set>>
    + Y : float <<get>> <<set>>
    + Z : float <<get>> <<set>>
}
struct RecordStruct <<sealed>> <<record>>  {
    + RecordStruct(X : float, Y : float, Z : float)
    + X : float <<get>> <<set>>
    + Y : float <<get>> <<set>>
    + Z : float <<get>> <<set>>
    + <<readonly>> <<override>> ToString() : string
    - <<readonly>> PrintMembers(builder : StringBuilder) : bool
    + {static} operator !=(left : RecordStruct, right : RecordStruct) : bool
    + {static} operator ==(left : RecordStruct, right : RecordStruct) : bool
    + <<readonly>> <<override>> GetHashCode() : int
    + <<readonly>> <<override>> Equals(obj : object) : bool
    + <<readonly>> Equals(other : RecordStruct) : bool
    + <<readonly>> Deconstruct(X : float, Y : float, Z : float) : void
    + RecordStruct()
}
enum LogLevel <<sealed>>  {
    Trace = 0
    Debug = 1
    Info = 2
    Warn = 3
    Error = 4
    Fatal = 5
    + LogLevel()
}
enum Accessibilities <<Flags>> <<sealed>>  {
    None = 0
    Public = 1
    Protected = 2
    Internal = 4
    ProtectedInternal = 8
    PrivateProtected = 16
    Private = 32
    All = Public | Protected | Internal | ProtectedInternal | PrivateProtected | Private
    + Accessibilities()
}

関連(Association)

ある型と他の型との関連付けは、個々の型定義の下に追加されます。関連付けを行う条件と付与する関連の種類は以下の通りです。

継承

対象の型がObject、ValueType、Struct以外の型を継承している場合、Inheritance (<|--)の関連を追加します。

public abstract class PlanetBase
{
}

public class Earth : PlanetBase
{
}
abstract class PlanetBase {
}

class Earth {
}

PlanetBase <|-- Earth

インターフェイスの実装

対象の型がインターフェイスを実装している場合、Realization (<|..)の関連を追加します。

interface ILogger 
{
}
class Logger : ILogger
{
}
interface ILogger {
}

class Logger {
}

ILogger <|.. Logger

プロパティまたはフィールドの関連

プロパティ、フィールドの型が出力対象の型の場合、Aggrigation (o--)またはComposition (*--)の関連を追加します。

次の条件を満たす場合にComposition、それ以外はAggrigationとなります。

  • プロパティやフィールドの初期化子で初期化している
  • コンストラクタ内で初期化している
class Class1
{
    public Item ItemA {get;} = new Item();  //初期化子で初期化
    public Item ItemB {get;}
    public Item ItemC {get;}
    public Class1(Item item) 
    {
        ItemB = new Item();  //コンストラクタで初期化
        ItemC = item;        //外部から注入
    }
}
class Class1 {
    + <<readonly>> ItemA : Item <<get>>
    + <<readonly>> ItemB : Item <<get>>
    + <<readonly>> ItemC : Item <<get>>
    + Class1(item : Item)
}
Class1 *-- Item : ItemA
Class1 *-- Item : ItemB
Class1 o-- Item : ItemC
Class1 ..> Item

配列型またはIEnumerable<T>を実装している型の場合

プロパティやフィールドの型との関連ではなく、要素の型との関連を追加します。その際、要素の型側に多重度"*"を付与します。

public abstract class PlanetBase(string name)
{
    public string Name { get; set; } = name;
    public IList<Moon> Moons { get; } = new List<Moon>();
    protected void AddMoon(Moon moon)
    {
        Moons.Add(moon);
    }
}
abstract class PlanetBase  {
    # PlanetBase(name : string)
    + Name : string <<get>> <<set>>
    + <<readonly>> Moons : IList<Moon> <<get>>
    # AddMoon(moon : Moon) : void
}
PlanetBase *-- "*" Moon : Moons
PlanetBase ..> Moon

メソッドのパラメータ

メソッドのパラメータの型が出力対象の型の場合に Dependency (..>)の関連を追加します。

class Parameters
{
   public int A {get;set;}
   public int B {get;set;}
}
class ClassA
{
    public void Execute(Parameters parameters)
    {
       Console.WriteLine($"({parameters.A},{parameters.B})");
    }
}

ネストした型

メンバーに型の定義が含まれる場合 Nested (+--) の関連を追加します。
ネストした型の名前は {親の型名}::{型名}の形式で付けられます。

C#
class Parent
{
    class ChiledA
    {
        class GrandchildA
        {
            class GreatGrandchild
            {

            }
        }
        class GrandchildB
        {
            class GreatGrandchild
            {

            }
        }
    }
    class ChildeB
    {
        class GrandchildA
        {
            class GreatGrandchild
            {
            }
        }
        class GrandchildB
        {

        }
    }
}
PlantUML
class Parent::ChiledA::GrandchildA::GreatGrandchild  {
    + GreatGrandchild()
}

class Parent::ChiledA::GrandchildB::GreatGrandchild  {
    + GreatGrandchild()
}

class Parent::ChiledA::GrandchildB  {
    + GrandchildB()
}
Parent::ChiledA::GrandchildB +.. Parent::ChiledA::GrandchildB::GreatGrandchild

class Parent::ChiledA::GrandchildA  {
    + GrandchildA()
}
Parent::ChiledA::GrandchildA +.. Parent::ChiledA::GrandchildA::GreatGrandchild

class Parent::ChiledA  {
    + ChiledA()
}
Parent::ChiledA +.. Parent::ChiledA::GrandchildA
Parent::ChiledA +.. Parent::ChiledA::GrandchildB

class Parent::ChildeB::GrandchildA::GreatGrandchild  {
    + GreatGrandchild()
}

class Parent::ChildeB::GrandchildA  {
    + GrandchildA()
}
Parent::ChildeB::GrandchildA +.. Parent::ChildeB::GrandchildA::GreatGrandchild

class Parent::ChildeB::GrandchildB  {
    + GrandchildB()
}

class Parent::ChildeB  {
    + ChildeB()
}
Parent::ChildeB +.. Parent::ChildeB::GrandchildA
Parent::ChildeB +.. Parent::ChildeB::GrandchildB

class Parent  {
    + Parent()
}
Parent +.. Parent::ChiledA
Parent +.. Parent::ChildeB

ファイル参照

各関連を追加すると同時に、!includeディレクティブを追加して、関連する相手方の型定義を参照するように設定します。

プロジェクトの設定PlantUmlGenerator_OutputDirで同じディレクトリを指定しておけば、別プロジェクトの出力ファイルであっても、フォルダ構成の規則に従って対応するファイルを探索しincludeされます。

例:

//Assembly: Consoto.App1.Core
namespace Consoto.App1.Core;
class Parameters
{
}
//Assembly: Consoto.App1
namespace Consoto.App1;
class ClassA
{
    public void Run(Parameter parameters)
    {
        //...
    }
}

上記のようなアセンブリと名前空間にクラスの定義がある場合、UMLファイルは下記のフォルダ構成で出力されます。

ClassAのUMLファイルを作る際にParametersとの関連がある場合、出力ディレクトリ内のParameters.pumlファイルを探索します。
ファイルが存在する場合に関連と!includeが追加されます。ファイルのパスはClassA.pumlからの相対パスになります。

@startuml ClassA
!include ../../../../Consoto.App1.Core/Consoto/App1/Core/Parameters.puml
class ClassA {
    + Run(parameters : Parameters) : void 
}
ClassA ..> Parameters
@enduml

出力例 

生成される1ファイルの出力例を示します。ファイルは以下のような構成になっています。

  • 関連クラスの!include
  • クラスの定義
  • 関連の定義
C#
namespace SourceGeneratorTest.Planets.BaseTypes;
public abstract class PlanetBase(string name)
{
    public string Name { get; set; } = name;
    public double Diameter { get; protected set; }
    public double Mass { get; protected set; }
    public double DistanceFromSun { get; protected set; }
    public double OrbitalPeriod { get; protected set; }
    public double SurfaceTemperature { get; protected set; }
    public IList<Moon> Moons { get; } = new List<Moon>();

    protected void AddMoon(Moon moon)
    {
        Moons.Add(moon);
    }
    public abstract Task Orbit();
    public abstract Task Rotate();
}
namespace SourceGeneratorTest.Planets;
public class Earth : PlanetBase
{
    public Earth() : base("Earth")
    {
        Diameter = 12742;
        Mass = 5.972e24;
        DistanceFromSun = 149.6e6;
        OrbitalPeriod = 365.26;
        SurfaceTemperature = 288;
        AddMoon(new Moon("Moon", 3474, 7.35e22, 384400));
    }

    public override async Task Orbit()
    {
        await Task.Delay(5000);
    }

    public override async Task Rotate()
    {
        await Task.Delay(5000);
    }
}
namespace SourceGeneratorTest.Planets;
public sealed class Moon(string name, double diameter, double mass, double distanceFromPlanet)
{
    public string Name { get; } = name;
    public double Diameter { get; } = diameter;
    public double Mass { get; } = mass;
    public double DistanceFromPlanet { get; } = distanceFromPlanet;
}
PlantUML
@startuml PlanetBase
!include ../Moon.puml
abstract class PlanetBase  {
    # PlanetBase(name : string)
    + Name : string <<get>> <<set>>
    + Diameter : double <<get>> <<protected set>>
    + Mass : double <<get>> <<protected set>>
    + DistanceFromSun : double <<get>> <<protected set>>
    + OrbitalPeriod : double <<get>> <<protected set>>
    + SurfaceTemperature : double <<get>> <<protected set>>
    + <<readonly>> Moons : IList<Moon> <<get>>
    # AddMoon(moon : Moon) : void
    + {abstract} Orbit() : Task
    + {abstract} Rotate() : Task
}
PlanetBase *-- "*" Moon : Moons
PlanetBase ..> Moon
@enduml
@startuml Earth
!include BaseTypes/PlanetBase.puml
class Earth  {
    + Earth()
    + <<override>> <<async>> Orbit() : Task
    + <<override>> <<async>> Rotate() : Task
}
PlanetBase <|-- Earth
@enduml
@startuml Moon
class Moon <<sealed>>  {
    + Moon(name : string, diameter : double, mass : double, distanceFromPlanet : double)
    + <<readonly>> Name : string <<get>>
    + <<readonly>> Diameter : double <<get>>
    + <<readonly>> Mass : double <<get>>
    + <<readonly>> DistanceFromPlanet : double <<get>>
}
@enduml
C#
namespace SourceGeneratorTest.Classes;
internal class SampleModel
{
    private readonly ILogger logger;
    private readonly IList<StructA> structures;

    public IReadOnlyList<Item> Items { get; } = new List<Item>();

    public SampleModel(ILogger logger, IList<StructA> structures)
    {
        this.logger = logger;
        this.structures = structures;
    }

    public async ValueTask Execute(Parameters parameters)
    {
        await Task.Delay(1000);
    }
}
namespace SourceGeneratorTest.Classes;
internal class Logger : ILogger
{
    public void Write(string message, LogLevel logLevel, Exception? exception = null)
    {
        Console.WriteLine($"[{logLevel}] {message}");
        if (exception != null)
        {
            Console.WriteLine(exception.Message);
        }
    }
    public void WriteDebug(string message) => Write(message, LogLevel.Debug);
    public void WriteInfo(string message) => Write(message, LogLevel.Info);
    public void WriteTrace(string message) => Write(message, LogLevel.Trace);
    public void WriteWarn(string message) => Write(message, LogLevel.Warn);
    public void WriteError(string message, Exception? exception = null) => Write(message, LogLevel.Error, exception);
    public void WriteFatal(string message, Exception? exception = null) => Write(message, LogLevel.Fatal, exception);
}
namespace SourceGeneratorTest.Library.Logs;
public interface ILogger
{
    void Write(string message, LogLevel logLevel, Exception exception);
    void WriteTrace(string message);
    void WriteDebug(string message);
    void WriteInfo(string message);
    void WriteWarn(string message);
    void WriteError(string message, Exception exception);
    void WriteFatal(string message, Exception exception);
}

public enum LogLevel
{
    Trace,
    Debug,
    Info,
    Warn,
    Error,
    Fatal
}
namespace SourceGeneratorTest.Library.Types;
public struct StructA()
{
    public float X { get; set; }
    public float Y { get; set; }
    public float Z { get; set; }
}

public record Item(string Name, double Value);

public record Parameters
{
    public int X { get; }
    public int Y { get; }
    public Parameters(int x, int y) => (X, Y) = (x, y);

    public int Area() => X * Y;
}
PlantUML
@startuml SampleModel
!include ../../../SourceGeneratorTest.Library/SourceGeneratorTest/Library/Logs/ILogger.puml
!include ../../../SourceGeneratorTest.Library/SourceGeneratorTest/Library/Types/StructA.puml
!include ../../../SourceGeneratorTest.Library/SourceGeneratorTest/Library/Types/Item.puml
!include ../../../SourceGeneratorTest.Library/SourceGeneratorTest/Library/Types/Parameters.puml
class SampleModel  {
    - <<readonly>> logger : ILogger
    - <<readonly>> structures : IList<StructA>
    + <<readonly>> Items : IReadOnlyList<Item> <<get>>
    + SampleModel(logger : ILogger, structures : IList<StructA>)
    + <<async>> Execute(parameters : Parameters) : ValueTask
}
SampleModel o-- ILogger : logger
SampleModel o-- "*" StructA : structures
SampleModel *-- "*" Item : Items
SampleModel ..> ILogger
SampleModel ..> Parameters
@enduml
@startuml Logger
!include ../../../SourceGeneratorTest.Library/SourceGeneratorTest/Library/Logs/ILogger.puml
!include ../../../SourceGeneratorTest.Library/SourceGeneratorTest/Library/Logs/LogLevel.puml
class Logger  {
    + Write(message : string, logLevel : LogLevel, exception : Exception?) : void
    + WriteDebug(message : string) : void
    + WriteInfo(message : string) : void
    + WriteTrace(message : string) : void
    + WriteWarn(message : string) : void
    + WriteError(message : string, exception : Exception?) : void
    + WriteFatal(message : string, exception : Exception?) : void
    + Logger()
}
ILogger <|.. Logger
Logger ..> LogLevel
@enduml
@startuml ILogger
!include ./LogLevel.puml
interface ILogger  {
    + Write(message : string, logLevel : LogLevel, exception : Exception) : void
    + WriteTrace(message : string) : void
    + WriteDebug(message : string) : void
    + WriteInfo(message : string) : void
    + WriteWarn(message : string) : void
    + WriteError(message : string, exception : Exception) : void
    + WriteFatal(message : string, exception : Exception) : void
}
ILogger ..> LogLevel
@enduml
@startuml LogLevel
enum LogLevel <<sealed>>  {
    Trace = 0
    Debug = 1
    Info = 2
    Warn = 3
    Error = 4
    Fatal = 5
    + LogLevel()
}
@enduml
@startuml Item
class Item <<record>>  {
    + Item(Name : string, Value : double)
    # <<readonly>> <<virtual>> EqualityContract : Type <<get>>
    + Name : string <<get>> <<set>>
    + Value : double <<get>> <<set>>
    + <<override>> ToString() : string
    # <<virtual>> PrintMembers(builder : StringBuilder) : bool
    + {static} operator !=(left : Item?, right : Item?) : bool
    + {static} operator ==(left : Item?, right : Item?) : bool
    + <<override>> GetHashCode() : int
    + <<override>> Equals(obj : object?) : bool
    + <<virtual>> Equals(other : Item?) : bool
    # Item(original : Item)
    + Deconstruct(Name : string, Value : double) : void
}
"IEquatable`1" "<Item>" <|.. Item
@enduml
@startuml StructA
struct StructA <<sealed>>  {
    + StructA()
    + X : float <<get>> <<set>>
    + Y : float <<get>> <<set>>
    + Z : float <<get>> <<set>>
}
@enduml
@startuml Parameters
class Parameters <<record>>  {
    # <<readonly>> <<virtual>> EqualityContract : Type <<get>>
    + <<readonly>> X : int <<get>>
    + <<readonly>> Y : int <<get>>
    + Parameters(x : int, y : int)
    + Area() : int
    + <<override>> ToString() : string
    # <<virtual>> PrintMembers(builder : StringBuilder) : bool
    + {static} operator !=(left : Parameters?, right : Parameters?) : bool
    + {static} operator ==(left : Parameters?, right : Parameters?) : bool
    + <<override>> GetHashCode() : int
    + <<override>> Equals(obj : object?) : bool
    + <<virtual>> Equals(other : Parameters?) : bool
    # Parameters(original : Parameters)
}
"IEquatable`1" "<Parameters>" <|.. Parameters
@enduml
18
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
13