1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SourceGeneratorでC#のソースコードからPlantUMLのクラス図を生成するツール(β版)

Last updated at Posted at 2024-03-02

PlantUmlClassDiagramGenerator.SourceGenerator 0.5.1-beta

PlantUmlClassDiagramGenerator.SourceGenerator がベータ版(0.5.1-beta) になりました。
主に、出力対象とするクラスやメンバーを制御する設定の追加や、ファイル出力時のフォルダ構成の見直しなどを行いました。

ソースコード

サンプル

.NET Architectureのサンプルリポジトリ"eShopOnWeb" のソースコードをまるっとクラス図にしてみました。

PlantUMLのコードはこちらにまとめて出力しています。
https://github.com/pierre3/eShopOnWeb/tree/main/generated-uml

レンダリングしたクラス図はこちらで確認できます。
https://github.com/pierre3/eShopOnWeb/tree/main/generated-uml/ClassDiagram

アルファ版 (0.1.8-alpha) からの変更点

前回の記事 からの変更点は以下の通りです。

使い方

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" />
	<CompilerVisibleProperty Include="PlantUmlGenerator_AttributeRequierd" />
	<CompilerVisibleProperty Include="PlantUmlGenerator_IncludeMemberAccessibilities" />
	<CompilerVisibleProperty Include="PlantUmlGenerator_ExcludeMemberAccessibilities" />
</ItemGroup>
<PropertyGroup>
	<PlantUmlGenerator_OutputDir>$(ProjectDir)generated-uml</PlantUmlGenerator_OutputDir>
	<PlantUmlGenerator_AttributeRequierd>false</PlantUmlGenerator_AttributeRequierd>
	<PlantUmlGenerator_IncludeMemberAccessibilities>All</PlantUmlGenerator_IncludeMemberAccessibilities>
	<PlantUmlGenerator_ExcludeMemberAccessibilities>None</PlantUmlGenerator_ExcludeMemberAccessibilities>
</PropertyGroup>
プロパティ 説明 既定値
PlantUmlGenerator_OutputDir 生成したUMLファイルの配置先ディレクトリを指定します。
異なるプロジェクト間で関連付けを行いたい場合は、関連する全てのプロジェクトで同じディレクトリを指定してください。
$(ProjectDir)generated_uml
PlantUmlGenerator_AttributeRequierd PlantUmlDiagramAttributeを付けた型のみを出力対象とする場合はtrueを、プロジェクト内で定義されている全ての型を出力の対象とする場合はfalseを指定します。 true
PlantUmlGenerator_IncludeMemberAccessibilities 出力対象に含めるメンバーのアクセシビリティを指定します。設定可能な値は下記の通り。
・None
・Public
・Protected
・Internal
・ProtectedInternal
・PrivateProtected
・Private
・All
カンマ区切りで複数の値を指定できます。
例: Public,Protected
All
PlantUmlGenerator_ExcludeMemberAccessibilities 出力対象外とするメンバーのアクセシビリティを指定します。指定の方法はPlantUmlGenerator_IncludeMemberAccessibilitiesと同様です。 None

3. 属性値の設定

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

PlantUmlDiagramAttribute

プロジェクトの設定PlantUmlGenerator_AttributeRequierdをtrueとした場合、出力対象としたい型にはこの属性を付加する必要があります。

[PlantUmlDiagram]
class ClassA
{
    public int X { get;set;}
    //....
}

また、IncludeMemberAccessibilitiesプロパティを設定することで表示対象とするメンバーのアクセシビリティを指定することができます。このプロパティはプロジェクトの設定 (PlantUmlGenerator_IncludeMemberAccessibilities)よりも優先されます。

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

逆にIncludeMemberAccessibilitiesプロパティを使って表示対象外とするメンバーを指定することもできます。

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

PlantUmlIgnoreAttribute

プロジェクトの設定PlantUmlGenerator_AttributeRequierdをfalseとした場合、プロジェクト内で定義されている全ての型が出力の対象となります。
一部の型を出力の対象外としたい場合は、その型にPlantUmlIgnoreAttributeを設定します。

[PlantUmlIgnore]
class ClassA
{
}

class ClassB
{

}

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

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

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

仕様

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
1
1
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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?