PlantUmlClassDiagramGenerator.SourceGenerator 1.0.0
C#のソースコードからPlantUmlのクラス図を生成するツールPlantUmlClassDiagramGenerator.SourceGenerator 1.0.0
をリリースしました。
最低限必要な機能が実装できたところでPreviewを外し正式版としましたが、引き続きフィードバックをお待ちしております。
ソースコード
0.6.0-beta からの変更点
自動的に付与される関連付けの作成を、その種類ごとに無効化する機能を追加しました。
リリースノート
-
PlantUmlDiagramAttribute
にDisableAssociationTypes
プロパティを追加 -
PlantUmlDiagramAttribute
のIncludeMemberAccessibilities
プロパティおよび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