クラス間の依存関係のリストアップ
はじめに
筆者は下記記事でC#の自動クラス図作成ツールを公開しています。初リリース時のツールでは検出できていない依存が多く、種類を整理するためにまとめました。初心者の勉強にもなると思います。
ちなみに、ここで紹介している依存はすべて私のアプリで(たぶん)拾えます。ぜひツールを使ってみてください。
Roslynを使用した静的解析により検出してます。Roslynを使用した検出方法をメモ書きしていますが気にしないでください。
依存関係一覧
| No. | 依存の種類 | 説明 |
|---|---|---|
| 1 | 継承 | 基底クラスへの依存 |
| 2 | インターフェース実装 | 実装インターフェースへの依存 |
| 3 | フィールド・プロパティの型 | メンバー変数の型への依存 |
| 4 | メソッドシグネチャ | 戻り値型・パラメータ型への依存 |
| 5 | オブジェクト生成(new) | インスタンス生成による依存 |
| 6 | 静的メソッド呼び出し | 静的メソッドを持つクラスへの依存 |
| 7 | 拡張メソッド呼び出し | 拡張メソッド定義クラスへの依存 |
| 8 | 静的プロパティ・フィールドアクセス | 静的メンバーを持つクラスへの依存 |
| 9 | ジェネリック型引数 | ジェネリックの型パラメータへの依存 |
| 10 | 例外処理(catch) | catch句の例外型への依存 |
| 11 | 例外スロー(throw) | throw文での例外型への依存 |
| 12 | 型キャスト | キャスト先の型への依存 |
| 13 | as演算子 | 型変換先への依存 |
| 14 | is演算子・パターンマッチング | 型チェック対象への依存 |
| 15 | typeof演算子 | リフレクション対象型への依存 |
| 16 | 属性(Attribute) | 属性クラスへの依存 |
| 17 | var宣言(型推論) | 推論された型への依存 |
| 18 | インスタンスメソッド呼び出し | 呼び出し元オブジェクトの型への依存 |
| 19 | ラムダ式内のメンバーアクセス | ラムダ内で使用される型への依存 |
| 20 | 配列型 | 配列要素の型への依存 |
| 21 | タプル型 | タプル要素の型への依存 |
| 22 | switch式のパターンマッチング | switch式内の型への依存 |
| 23 | nameof演算子 | nameof対象の型への依存 |
| 24 | default演算子 | デフォルト値の型への依存 |
| 25 | ジェネリック制約(where句) | 制約に指定された型への依存 |
各依存関係の詳細
1. 継承
基底クラスを継承することで、そのクラスへの依存関係が生じます。
public class CustomerRepository : BaseRepository
{
// CustomerRepository は BaseRepository に依存
}
検出方法: INamedTypeSymbol.BaseType から基底クラスを取得
2. インターフェース実装
インターフェースを実装することで、そのインターフェースへの依存関係が生じます。
public class CustomerService : ICustomerService, IDisposable
{
// CustomerService は ICustomerService と IDisposable に依存
}
検出方法: INamedTypeSymbol.Interfaces から実装インターフェースを取得
3. フィールド・プロパティの型
クラスのフィールドやプロパティの型として使用されるクラスへの依存です。
public class OrderService
{
private readonly ICustomerRepository _customerRepository; // 依存: ICustomerRepository
public OrderDto CurrentOrder { get; set; } // 依存: OrderDto
private List<ProductInfo> _products; // 依存: List<ProductInfo>, ProductInfo
}
検出方法:
-
IFieldSymbol.Typeからフィールドの型を取得 -
IPropertySymbol.Typeからプロパティの型を取得
4. メソッドシグネチャ
メソッドの戻り値型やパラメータ型への依存です。
public class OrderService
{
// 依存: OrderDto(戻り値), int(プリミティブのため除外), CustomerInfo(パラメータ)
public OrderDto CreateOrder(int customerId, CustomerInfo info)
{
// ...
}
}
検出方法:
-
IMethodSymbol.ReturnTypeから戻り値型を取得 -
IMethodSymbol.Parametersからパラメータ型を取得
5. オブジェクト生成(new)
new 演算子でインスタンスを生成する際の依存です。
public void ProcessOrder()
{
var customer = new CustomerDto(); // 依存: CustomerDto
var orders = new List<OrderInfo>(); // 依存: List<OrderInfo>, OrderInfo
}
検出方法: ObjectCreationExpressionSyntax を解析し、GetSymbolInfo から型を取得
6. 静的メソッド呼び出し
静的メソッドを呼び出すことで、そのメソッドを持つクラスへの依存が生じます。
public void ValidateData(string input)
{
if (ValidationHelper.IsValid(input)) // 依存: ValidationHelper
{
// ...
}
}
検出方法: InvocationExpressionSyntax から IMethodSymbol を取得し、IsStatic をチェック
7. 拡張メソッド呼び出し
拡張メソッドを使用することで、その拡張メソッドを定義しているクラスへの依存が生じます。
public void ProcessJson(JObject jObject)
{
// 依存: JObjectExtensions(AssignIfExistsを定義しているクラス)
jObject.AssignIfExists<string>("key", val => Process(val));
}
検出方法: IMethodSymbol.IsExtensionMethod をチェックし、ContainingType を取得
8. 静的プロパティ・フィールドアクセス
静的なプロパティやフィールドにアクセスすることで依存が生じます。
public void ConfigureSettings()
{
var apiKey = AppSettings.ApiKey; // 依存: AppSettings
var format = FormatKeys.Id; // 依存: FormatKeys
}
検出方法: MemberAccessExpressionSyntax から IPropertySymbol または IFieldSymbol を取得し、IsStatic をチェック
9. ジェネリック型引数
ジェネリック型やメソッドの型引数として指定される型への依存です。
public void ProcessData()
{
var list = new List<CustomerDto>(); // 依存: List<T>, CustomerDto
var dict = new Dictionary<string, OrderInfo>(); // 依存: Dictionary<K,V>, OrderInfo
var result = SomeMethod<ProductData>(); // 依存: ProductData
}
検出方法:
-
INamedTypeSymbol.TypeArgumentsからジェネリック型引数を取得 -
IMethodSymbol.TypeArgumentsからメソッドの型引数を取得
10. 例外処理(catch)
catch句で例外型を指定することで、その例外型への依存が生じます。
public void ProcessData()
{
try
{
// ...
}
catch (MyException ex) // 依存: MyException
{
// ...
}
catch (InvalidOperationException) // 依存: InvalidOperationException
{
// ...
}
}
検出方法: CatchClauseSyntax.Declaration.Type から例外型を取得
11. 例外スロー(throw)
throw文で例外をスローする際の依存です。
public void ValidateInput(string input)
{
if (string.IsNullOrEmpty(input))
{
throw new ValidationException("Input is required"); // 依存: ValidationException
}
throw new MyException($"Error: {input}", ex); // 依存: MyException
}
検出方法: throw文内の ObjectCreationExpressionSyntax を検出
12. 型キャスト
明示的な型キャストによる依存です。
public void ProcessData(object data)
{
var customer = (CustomerDto)data; // 依存: CustomerDto
var order = (IOrderInfo)GetOrder(); // 依存: IOrderInfo
}
検出方法: CastExpressionSyntax.Type から型を取得
13. as演算子
安全な型変換(as演算子)による依存です。
public void ProcessData(object data)
{
var customer = data as CustomerDto; // 依存: CustomerDto
if (customer != null)
{
// ...
}
}
検出方法: BinaryExpressionSyntax で IsKind(SyntaxKind.AsExpression) をチェック
14. is演算子・パターンマッチング
型チェックとパターンマッチングによる依存です。
public void ProcessData(object data)
{
// 従来のis演算子
if (data is CustomerDto) // 依存: CustomerDto
{
// ...
}
// パターンマッチング
if (data is OrderInfo order) // 依存: OrderInfo
{
ProcessOrder(order);
}
// プロパティパターン
if (data is ProductData { IsActive: true }) // 依存: ProductData
{
// ...
}
}
検出方法:
-
BinaryExpressionSyntaxでIsKind(SyntaxKind.IsExpression)をチェック -
IsPatternExpressionSyntaxからパターン内の型を取得
15. typeof演算子
リフレクションで型情報を取得する際の依存です。
public void RegisterType()
{
var type = typeof(CustomerDto); // 依存: CustomerDto
container.Register(typeof(ICustomerService)); // 依存: ICustomerService
}
検出方法: TypeOfExpressionSyntax.Type から型を取得
16. 属性(Attribute)
クラスやメンバーに付与される属性による依存です。
[Serializable] // 依存: SerializableAttribute
[CustomValidation(ErrorMessage = "Invalid")] // 依存: CustomValidationAttribute
public class CustomerDto
{
[Required] // 依存: RequiredAttribute
[StringLength(100)] // 依存: StringLengthAttribute
public string Name { get; set; }
[Obsolete("Use NewMethod instead")] // 依存: ObsoleteAttribute
public void OldMethod() { }
}
検出方法: AttributeSyntax から GetSymbolInfo で属性クラスを取得
17. var宣言(型推論)
var宣言で型推論される際の依存です。
public void ProcessData()
{
var customer = new CustomerDto(); // 依存: CustomerDto(型推論)
var result = GetOrderData(); // 依存: GetOrderDataの戻り値型
var list = GetCustomers(); // 依存: List<CustomerInfo>(推論結果)
}
検出方法: VariableDeclarationSyntax.Type.IsVar をチェックし、Initializer.Value の型を取得
18. インスタンスメソッド呼び出し
インスタンスのメソッドを呼び出すことで、そのインスタンスの型への依存が生じます。
public void ProcessOrder(CustomerRepository repository)
{
var customer = repository.GetCustomer(id); // 依存: CustomerRepository
var order = new OrderService();
order.ProcessOrder(customerId); // 依存: OrderService
}
検出方法: InvocationExpressionSyntax の Expression が MemberAccessExpressionSyntax の場合、左辺の型を取得
19. ラムダ式内のメンバーアクセス
ラムダ式内で使用される型への依存です。
public void ProcessData(JObject jObject)
{
// ラムダ式内でtoolDataのメンバーにアクセス
jObject.AssignIfExists<string>("key", val => toolData.GlobalId = val);
// ^^^^^^^^ toolDataの型への依存
var filtered = customers.Where(c => c.IsActive); // 依存: Customer
var mapped = orders.Select(o => new OrderDto
{
Id = o.Id, // 依存: Order
Total = Calculator.CalculateTotal(o) // 依存: Calculator
});
}
検出方法: SimpleLambdaExpressionSyntax や ParenthesizedLambdaExpressionSyntax 内の DescendantNodes() を解析
20. 配列型
配列の要素型への依存です。
public void ProcessData()
{
var customers = new CustomerDto[10]; // 依存: CustomerDto
string[] names = GetNames(); // 依存: string(プリミティブのため除外)
IOrderInfo[] orders = new OrderInfo[5]; // 依存: IOrderInfo, OrderInfo
}
検出方法: IArrayTypeSymbol.ElementType から要素型を取得
21. タプル型
タプルの要素型への依存です。
public (CustomerDto customer, OrderInfo order) GetData()
{
// 依存: CustomerDto, OrderInfo
return (new CustomerDto(), new OrderInfo());
}
public void ProcessData()
{
var (customer, order) = GetData(); // タプル分解
var tuple = (Name: "John", Age: 30); // 値タプル
}
検出方法: タプル型の要素を再帰的に処理
22. switch式のパターンマッチング
switch式内で使用される型への依存です。
public string ProcessData(object data)
{
return data switch
{
CustomerDto customer => customer.Name, // 依存: CustomerDto
OrderInfo order => order.OrderNumber, // 依存: OrderInfo
ProductData product => product.Code, // 依存: ProductData
_ => "Unknown"
};
}
検出方法: SwitchExpressionSyntax と SwitchExpressionArmSyntax 内のパターンを解析
23. nameof演算子
nameof演算子で参照される型への依存です。
public void LogError()
{
var typeName = nameof(CustomerDto); // 依存: CustomerDto(弱い依存)
var propName = nameof(CustomerDto.Name); // 依存: CustomerDto
Console.WriteLine($"Error in {nameof(OrderService)}"); // 依存: OrderService
}
検出方法: InvocationExpressionSyntax で nameof を検出し、引数から型を取得
24. default演算子
default演算子で型を指定する際の依存です。
public CustomerDto GetCustomer()
{
return default(CustomerDto); // 依存: CustomerDto
}
public void ProcessData<T>()
{
var value = default(T); // ジェネリック型パラメータ
return default; // C# 7.1以降、型推論
}
検出方法: DefaultExpressionSyntax から型を取得
25. ジェネリック制約(where句)
ジェネリック型の制約に指定される型への依存です。
public class Repository<T> where T : BaseEntity, IEntity
{
// 依存: BaseEntity, IEntity
}
public void ProcessData<T>(T data)
where T : ICustomerInfo, IDisposable
{
// 依存: ICustomerInfo, IDisposable
}
検出方法: TypeParameterConstraintClauseSyntax から制約型を取得