読者です 読者をやめる 読者になる 読者になる

(補足) Roslyn で C#のソースコードからPlantUMLのクラス図を生成する の設計メモ

Roslyn C# PlantUML

前回の記事の補足資料です。

pierre3.hatenablog.com

目次

使い方

前回、PlantUmlClassDiagramGenerator の使い方を書くのを忘れていましたので載せておきます。

PlantUmlClassDiagramGenerator はコンソールアプリケーションです。以下のパラメータを受け付けます。

PlantUmlClassDiagramGenerator.exe SOURCE_PATH [DEST_DIR]

  • 第1引数: SOURCE_PATH
    • 変換元のソースファイル名または、ソースファイルを格納したフォルダ名を指定します。
    • フォルダを指定した場合は、直下にある .cs ファイルをすべて読み込みます。(フォルダの階層は辿りません)
  • 第2引数: DEST_DIR
    • 結果のPlantUMLファイルの出力先を指定します。省略可能です。
    • 省略した場合は、入力ファイルと同じ階層に"uml" というフォルダを作成してそこに出力します。
C:\ > PlantUmlClassDiagramGenerator.exe SOURCE_PATH DEST_DIR

設計メモ

C#からPlantUMLのクラス図に変換する処理の設計メモです。

  • UMLに関する知識不足等で、不適切な部分があるかもしれません。 お気づきの点などありましたらご指摘頂けると嬉しいです。

型定義

インターフェース、クラス、構造体などの型定義に関する仕様です。
C#のキーワードとPlantUMLでの記述との対応付けを表にしてみました。

型キーワード

PlantUMLでは、interface , class , abstract class , enum が使用可能です。

C# PlantUML Memo
class class
struct <<struct>> class PlantUMLにはstruct で定義できる型が無いので、class にステレオタイプ<<struct>> を付加することで構造体を表現
interface interface
abstract class abstract class 抽象クラスはabstract class で宣言可能
enum enum

修飾子

修飾子は、(基本的には)ステレオタイプで表現します。

C# PlantUML Memo
abstract abstract ‘abstract‘ は、‘class‘ キーワードと組み合わせて抽象クラスの宣言時に使用する
static <<static>>
partial <<partial>>
sealed <<sealed>>
  • 型のアクセス修飾子について
    UMLに型自体のアクセス修飾子に関する規定がない(?)ため、型に対するアクセス修飾子は無視するようにしました。
    必要な場合は、<<internal>> class ClassA の様にステレオタイプを付加する?

型引数

ジェネリックの型引数は、PlantUMLでもC#と同じように書けます

class GenericsType<string,int> { 
}

クラス定義の変換例です。

//csharp
sealed class ClassA{ }
abstract class AbstractClass{ }
static class StaticClass{ }
struct Structure{ }
enum EnumType{ }
'plantuml
@startuml
class ClassA <<sealed>>
abstract class AbstractClass
class StaticClass <<static>>
class Structure <<struct>>
enum EnumType
class GenericsType<string,int>
@enduml

http://pierre3net.azurewebsites.net/Content/image/types.svg


メンバー定義

フィールド、プロパティ、メソッドEnum定数 など型のメンバー定義に関する仕様です。

アクセス修飾子(共通)

C# PlantUML Comment
public +
internal <<internal>> ~ (package) が意味合い的に近いと思うが、 protected internal と合わせてステレオタイプにした
protected internal # <<internal>> #~ はPlantUMLではエラーになるので internal をステレオタイプで表現することに
protected #
private -

修飾子 (共通)

C# PlantUML Comment
abstract {abstract} ステレオタイプではなく{}で括る。イタリック体の表記になる
static {static} ステレオタイプではなく{}で括る。下線付きの文字で表現される
virtual <<virtual>>
override <<override>>
readonly <<readonly>>

プロパティ

プロパティの表現は悩みどころですが、変換のしやすさを優先して以下の様に出力するようにしました。

  • C#のコードに記述されているアクセサー(get,set)をそのままステレオタイプとして出力
//csharp
public int PropA {get; set;}
public int PropB {get;}
public int PropC {get; protected set;}
'prantuml
+ PropA : int <<get>> <<set>>
+ PropB : int <<get>>
+ PropC : int <<get>> <<protected set>>
  • 別候補

他にも以下のように変換することも考えたのですが
(1) は protected set 等、アクセサーごとにアクセス修飾子が付けられた際の表現が難しく、 (2) は 変換処理が少し複雑になるのと、もはやプロパティではなくなるので却下しました。

'prantuml

'(1) <<property>> ステレオタイプを付け、getterのみの場合はreadonlyの制約を付ける
+ PropA : int <<property>>
+ PropB : int <<property>> { readonly }

'(2) Java風にメソッドで表現
+ getPropA():int
+ setPropA(value:int)
+ getPropB():int
+ getPropC():int
# setPropC(value:int)

初期化子(フィールド、プロパティ)

フィールド、プロパティの初期値を初期化子で設定している場合、初期値がリテラルの場合のみ = (初期値) を付加するようにしています。

リテラルのみにした理由は、初期化子にコンストラクタメソッドを使った場合に、 PlantUMLが 初期化子の() を見て フィールド(プロパティ)ではなくメソッドと判断してしまう為です。

'plantuml
''次のようにフィールドを記述しても、PlantUMLはメソッドと判断してしまう
class ClassA{
    # int : IList<int> = new List<int>()
}

初期化子の変換例

//csharp
class ClassA 
{
    private readonly int intField = 100;
    protected double X = 0, Y = 1, Z = 2;
    internal double PropC { get; } = 3.141592;
    protected IList<int> list = new List<int>();
}
'plantuml
class ClassA {
    - <<readonly>> intField : int = 100
    # X : double = 0
    # Y : double = 1
    # Z : double = 2
    <<internal>> PropC : double <<get>> = 3.141592;
    ' リテラル以外の初期化子は出力しない
    # list : IList<int>
}

未対応

現在対応できていないコードについてまとめます。

ネストクラス

現状、入れ子になった型の定義は、以下の様にそのまま入れ子の状態で出力されるのですが、PlantUMLではエラーとなり変換できません。

'plantuml
'' このコードはエラーになります
class NestedClass {
    + A : int <<get>>
    + B : InnerClass <<get>>
    class InnerClass {
        + X : string <<get>>
        + MethodX() : void
    }
}

内部クラスを外に出して、+-- で結ぶと入れ子を表現可能なので、以下の様に変換すべきなのですが。。。

class NestedClass {
    + A : int <<get>>
    + B : InnerClass <<get>>
}
class InnerClass {
    + X : string <<get>>
    + MethodX() : void
}
NestedClass +-- InnerClass

http://pierre3net.azurewebsites.net/Content/image/NestedClass.svg

演算子オーバーロード

演算子オーバーロードを定義してもPlantUMLに出力されません。(未検討)


変換例

最後に、いろいろな変換例を載せておきます。

変換前

変換元C#のコードはこちらを参照ください InputClasses.cs

変換後のPlantUML
'plantuml
class ClassA {
    - <<readonly>> intField : int = 100
    - {static} strField : string
    # X : double = 0
    # Y : double = 1
    # Z : double = 2
    - list : IList<int>

    # PropA : int <<get>>
    # <<internal>> PropB : string <<get>> <<protected set>>
    <<internal>> PropC : double <<get>> = 3.141592
    + ClassA()
    {static} ClassA()
    # <<virtual>> VirtualMethod() : void
    + <<override>> ToString() : string
    + {static} StaticMethod() : string
}
abstract class ClassB {
    - field_1 : int
    {abstract} + PropA : int <<get>> <<protected set>>
    # <<virtual>> VirtualMethod() : string
    + {abstract} AbstractMethod(arg1:int, arg2:double) : string
}
class ClassC <<sealed>> {
    - {static} <<readonly>> readonlyField : string = "ReadOnly"
    + <<override>> PropA : int <<get>> <<protected set>> = 100
    + <<override>> AbstractMethod(arg1:int, arg2:double) : string
    # <<override>> VirtualMethod() : string
}
class Vector <<struct>> {
    + X : double <<get>>
    + Y : double <<get>>
    + Z : double <<get>>
    + Vector(x:double, y:double, z:double)
    + Vector(source:Vector)
}
enum EnumA {
    AA= 0x0001,
    BB= 0x0002,
    CC= 0x0004,
    DD= 0x0008,
    EE= 0x0010,
}

ClassB <|-- ClassC
クラス図

http://pierre3net.azurewebsites.net/Content/image/inputClasses.svg