VContainer用のメソッドインジェクション用のスニペット(VS2022向け)

はじめに

VContainer を使用して [Inject] 属性を付けてインジェクションする場合、以下のような事を検討します。

  • VContainer が存在しない環境を考慮して設定用メソッドが必要?
    • つまりフィールドインジェクションだけだと不足
  • Constructメソッドでインジェクションするのが良い?
    • プロパティは値の設定が任意のオプション扱い的側面があるので設定されない可能性に配慮

という条件を考慮すると実装は概ね以下となります。

public class Sample
{
    ISampleServiceA _sampleServiceA;
    ISampleServiceB _sampleServiceB;

    // こんな感じにConstructを記述する
    [Inject]
    public void Construct(
        ISampleServiceA sampleServiceA,
        ISampleServiceB sampleServiceB)
    {
        InjectHelper.SetValueIfThrowNull(ref _sampleService, sampleServiceA);
        InjectHelper.SetValueIfThrowNull(ref _sampleServiceB, sampleServiceB);
    }
}

で、この記述がクラスごとに現れるのでボイラープレート(=定型的な記述)なので実装の手間を省くために VisualStduio で使えるスニペットを作成してみました。

確認環境

  • VisualStudio 2022
  • Unity 6000.2.10f1
  • VCoitainer v1.17.0

スニペット

スニペットは以下の通り

コード上にマウスホバー時の [Copy] ボタンからコードをクリップボードにコピーできます

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Inject Field(for VContainer)</Title>
      <Shortcut>inject</Shortcut>
      <Description>VContainer向けのInject用フィールド+Constructメソッドを生成します。</Description>
      <Author>Takap</Author>
    </Header>
    <Snippet>
      <Declarations>
        <Literal>
          <ID>type</ID>
          <Default>InjectType</Default>
          <ToolTip>インジェクトする型を指定します。</ToolTip>
        </Literal>
        <Literal>
          <ID>name</ID>
          <Default>value</Default>
          <ToolTip>変数名と引数名(共通)を指定します。</ToolTip>
        </Literal>
      </Declarations>
      <Code Language="csharp"><![CDATA[
        $type$ _$name$;

        [Inject]
public void Construct($type$ $name$)
{
    InjectHelper.SetValueIfThrowNull(ref _$name$, $name$);
}$end$
      ]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

inject と言うキーワードで候補が出るので候補が出ていると時に Tab を入力しスニペット入力を開始できます。

動作イメージは以下の通りです。

使用しているヘルパークラスは以下の通りです。

// 設定を補助するヘルパークラス
public static class InjectHelper
{
    // 多重設定禁止 + null 設定不可をチェックしつつ値を設定する
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void SetValueIfThrowNull<T>(ref T target, T value) where T : class
    {
        if (target != null)
            throw new InvalidOperationException($"{typeof(T).Name} is already initialized.");
        target ??= value ?? throw new ArgumentNullException(nameof(value));
    }
}

引数は 1つのみサポートしてます。複数必要になる場合、あとから手で書き加えていきます。

以上