Моя простая реализация DI-контейнера [открыть] - PullRequest
0 голосов
/ 28 апреля 2020

Я решил сделать свой собственный легкий DI-контейнер.

Я знаю о Zenject, et c. Я хотел очень легкий аналог.

Меня беспокоит несколько вещей:

1) Правильно ли использовать struct вместо класса для ссылки в этом контексте?

2) каждый раз, когда вы получаете доступ к _player.Reference, происходит проверка на ноль (в UnityEngine. После уничтожения Объекта не равняется нулю, поэтому используйте SafeIsUnityNull)

3) Двойная точка. Можно ли перегрузить оператор, чтобы вместо _player.Reference.AAAAAAAAAAAAA(); он работал _player.AAAAAAAAAAAAA(); ??

Сам контейнер:

    public static class DIContainer
    {

        private static readonly Dictionary<System.Type, Object> _references = new Dictionary<System.Type, Object>();

        public static void Bind<T>(T value) where T : Object
        {

            _references[typeof(T)] = value;

        }

        public static T Resolve<T>() where T : Object
        {

            if (_references.TryGetValue(typeof(T), out Object value))
                return (T)value;

            throw new System.InvalidCastException($"DIContainer: invalid resolve object {typeof(T)}");

        }

    }

Ссылка на поле:

    public struct DIReference<T> where T : Object
    {

        private T _reference;
        public T Reference => !_reference.SafeIsUnityNull() ? _reference : _reference = DIContainer.Resolve<T>();

    }

Установщик:

    public abstract class SceneInstaller : MonoBehaviour
    {
        private void Awake() => InstallBindings();
        protected abstract void InstallBindings();

    }
public class LevelInstaller : SceneInstaller
{


}

Использование

Связывание:

    [SerializeField] private PlayerController _player;

    protected override void InstallBindings()
    {

        DIContainer.Bind(_player);

    }

Ссылка:

public class Test
{

    private DIReference<PlayerController> _player;

    private void Foo()
    {

        _player.Reference.AAAAAAAAAAAAA();

    }

}

1 Ответ

0 голосов
/ 28 апреля 2020

Несколько точек:

  • Нельзя указать ограничение как where T : Object, поскольку каждый тип прямо или косвенно наследуется от System.Object; однако вы можете принудительно использовать ссылочные типы с помощью where T : class.

  • Вы не можете перегрузить оператор точки; однако вы можете перегрузить операторы explicit и implicit:

    public struct DIReference<T> where T : class
    {
        private T _reference;
        public T Reference => !_reference.SafeIsUnityNull()
            ? _reference
            : _reference = DIContainer.Resolve<T>();
    
        public static implicit operator T(DIReference<T> reference) => reference._reference;
    }
    

    Это позволяет вам выполнять такие действия, как

    _player.Reference.AAAAAAAAAAAAA();
    
    // Implicitly convert from DIReference<PlayerController> to PlayerController:
    PlayerController pc = _player; 
    pc.AAAAAAAAAAAAA();
    
    //or
    
    ((PlayerController)_player).AAAAAAAAAAAAA();
    

    Вместо перегрузки explicit вы вынуждены использовать последний вариант.

  • Изменчивые структуры неприятны. Ваша структура доступна только для чтения, но не является неизменной. При доступе из коллекции вы получаете копию исходной структуры, и все изменения в этой копии не записываются обратно в исходное значение в коллекции.

    Предположим, что у вас есть List<DIReference<T>> refList. Если сейчас вы напишите var ref = refList[i].Reference;, то вновь разрешенная ссылка не будет сохранена обратно в refList !. Вы должны написать:

    var diRef = refList[i];
    var ref = diRef.Reference; // May resolve the _reference field from DIContainer!
    refList[i] = diRef;
    
...