(補足) Roslyn で C#のソースコードからPlantUMLのクラス図を生成する の設計メモ
前回の記事の補足資料です。
目次
使い方
前回、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
メンバー定義
フィールド、プロパティ、メソッド、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
演算子のオーバーロード
演算子のオーバーロードを定義しても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