2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Unity・C#・Attribute拡張】自作DIコンテナーを作る話

Posted at

前書き

軽いDIコンテナを作る話です。そのため初学者向けの内容となります

Attributeでこんなことできますってサンプルコードにもなっていると思うので、Attribute拡張に触れたことない人は参考にしてみてください

経緯

挑戦と挫折

DIContainerを導入しようと思ったことがありました

候補として
・Zenject
・VContainer
などがありますね

しかし断念しました。
理由としては
・前提としてアーキテクチャに組み込む必要性がある
・今まで携わったプロジェクトでDIContainerの保守性が最悪だった
 ・前任者のインターフェイスの切り分け方が酷かった
・設計者の思想を理解しないと使えない場合が多い
・寿命の紐づけがわかりずらい
・疎結合になる関係上、コードで追いづらくなってしまう
・注入するオブジェクトの定義が面倒くさい

ざっくりまとめると、
難しくて気軽に使えないよねー
でも使いたい!

どんな時に?

・UIからデータ参照するときの流れをシンプルにしたい
・APIの呼び出しを簡略化したい
・シングルトンを減らしたい
↑この辺はInterface定義して中身をダミーに切り替えてテストしたりとかもできますよね

それ以外にもUnity側の機能を

[Inject] public UnitySoundManager soundManager;

みたいな感じで気軽に取ってこれるようになりたい!

ゆえに使いたい!

そして...

既存のDIContainerの問題点
・使い勝手がシンプルじゃない
・組み込む際にアーキテクチャも一緒に考えないといけない
・部分的に使うことはできるが、そのための定義とか面倒くさい

これらを解決するためには....

そうだ、自作しよう

設計

めっちゃざっくりしたクラス図

コードに起こしてみた

interfaceとAttribute

using System;

namespace TyInjector
{
    /// 属性の定義
    [System.AttributeUsage(System.AttributeTargets.Field)  ] 
    public class TyInjectAttribute : System.Attribute
    {
        public TyInjectAttribute()
        {
        }
    }

    /// インターフェイス
    public interface IInjectable : IDisposable
    {
        void Inject();
    }
}

MonoBehaviourで使えるように基底クラスを定義

using UnityEngine;

namespace TyInjector
{
    /// Inject可能なMonoBehaviour
    public abstract class TyInjectableMonoBehaviour<T> : MonoBehaviour, IInjectable
    {
        /// Awake時にContainerにインスタンスを登録する
        public void Awake()
        {
            Inject();
        }
        
        /// Destroy時にContainerのインスタンスを削除する
        public void OnDestroy()
        {
            Dispose();
        }

        public void Inject()
        {
            TyContainer.Register<T>(this);
        }

        public void Dispose()
        {
            TyContainer.Unregister<T>(this);
        }
    }
}

コンテナ本体

using System;
using System.Collections.Generic;

namespace TyInjector
{
    public class TyContainer : SingletonBehaviour<TyContainer>
    {
        /// コンテナに登録されたInstance群
        public Dictionary<Type, object> instances = new Dictionary<Type, object>();

        //注入処理
        public static void Inject<T>(T instance)
        {
            // 定義されているフィールドの取得、publicしか取れない
            var fields = instance.GetType().GetFields();
            foreach (var field in fields)
            {
                //inject属性がついている変数を探す
                var injectable = field.GetCustomAttributes(typeof(TyInjectAttribute), true);
                if (injectable.Length > 0)
                {
                    var fieldType = field.FieldType;
                    if (Instance.instances.ContainsKey(fieldType))
                    {
                        //オブジェクトを注入する
                        field.SetValue(instance, Instance.instances[fieldType]);
                    }
                }
            }
        }
        
        public void RegisterInternal<T>(object instance)
        {
            instances[typeof(T)] = instance;
        }
        
        public void UnregisterInternal<T>(object instance)
        {
            instances[typeof(T)] = instance;
        }
        
        public static void Register<T>(object instance)
        {
            Instance.RegisterInternal<T>(instance);
        }
        
        public static void Unregister<T>(object instance)
        {
            Instance.UnregisterInternal<T>(instance);
        }
    }
}

サンプルコード

using TyInjector;
using UnityEngine;


public class InjectTest : TyInjectableMonoBehaviour<InjectTest>
{
    public int value = 100;
}

public class Sample : MonoBehaviour
{
    [TyInject] public InjectTest injectTest;

    private void Awake()
    {
        TyContainer.Inject(this);
    }
    
    public void Start()
    {
        Debug.Log(injectTest.value);
    }
}

まとめ

interfaceいらなくね?
って思いながら作ってました

とりあえず最初に設計したもから忠実に作ってみました
現状のコードでは無駄が多く、インスタンスのチェックが薄いですね...

とはいえ、自分のやりたいことの要件の最低限はできている感じです
これをもう少し改良して、MonoBehaviourに特化したDIContainerみたいな仕組みにしていきたいですね

2
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?