ReactiveUI 依赖注入构造器

ReactiveUI Dependency Injection Constructor

我在 ReactiveUI 中使用内置依赖项 injector/splat 注入器。

我有构造函数,我想在其中传递适用的数据存储库。

在其他框架中,它仅使用接口反射,并使用 GetServices 调用来满足这些构造函数要求。例如,目前我有这个辅助扩展方法来创建我的 class:

    /// <summary>
    /// Helper class for having a object's constructor automatically assigned by a "GetService" request.
    /// </summary>
    /// <param name="resolver">The resolver.</param>
    /// <param name="type">The type to register.</param>
    public static void Register<TConcrete, TInterface>(this IMutableDependencyResolver resolver)
        where TConcrete : class
    {
        var concreteType = typeof(TConcrete);

        // Must be a single constructor
        var constructors = concreteType.GetConstructors().Single();

        IList<object> values = new List<object>();

        foreach (var parameter in constructors.GetParameters())
        {
            if (parameter.ParameterType.IsInterface == false)
            {
                throw new InvalidOperationException($"The type {concreteType.Name} has constructor paramters that are not interfaces.");
            }

            values.Add(resolver.GetService(parameter.ParameterType));
        }

        resolver.Register(() => Activator.CreateInstance(concreteType, values.ToArray()), typeof(TInterface));
    }

我使用该助手 class 的原因是为了避免在我的 AppBootStrapper 中执行以下操作:

        dependencyResolver.Register(() => new SetupFlightViewModel(dependencyResolver.GetService<IScreen>(), dependencyResolver.GetService<IFlightsModel>(), dependencyResolver.GetService<IAirportsModel>()), typeof(ISetupFlightViewModel));

只是检查以确保我没有对 Splat/ReactiveUI 框架做任何不明显的事情,它已经提供了。我意识到执行上述操作会产生性能成本,并且可能会使用表达式树编译表达式来避免每次反射成本或其他事情。

感谢您的帮助, 格伦

Splat 依赖解析器(服务注册表)是开箱即用的非常基本的工具,它不提供依赖注入 (DI)。

但是如果你喜欢 DI(不是每个人都喜欢,因为它可以隐藏依赖性和设计的复杂性,有些人更愿意感受它的痛苦以更好地避免它),你可以轻松地将 DI 插入其中,因为你只是做了。

尽管对你的 impl 有一个评论,我建议你延迟对 GetService 的实际调用,直到创建对象(以防你的服务注册表内容随时间变化,并避免强制 Register排序),例如:

var paramType = parameter.ParameterType;
values.Add(() => resolver.GetService(paramType));


... Activator.CreateInstance(concreteType, values.Select(cb => cb()).ToArray()) ...

我刚开始使用 ReactiveUI 并尝试做同样的事情。我在 Whosebug 上找到了这个 post 并找到了解决方案。稍微充实一下之后,我想我应该分享我的代码。由于这是我的搜索中出现的第一个结果,我想我 post 将结果放在这里,这样任何遵循相同路径的人都可以从我的劳动成果中受益。

public static class Bootstrapper
{
    public static void RegisterDependencies()
    {
        Locator.CurrentMutable.RegisterLazySingleton<ISettingsService>(CreateWithConstructorInjection<InMemorySettingsService>);
        Locator.CurrentMutable.Register<MainWindowViewModel>(CreateWithConstructorInjection<MainWindowViewModel>);
    }

    private static T CreateWithConstructorInjection<T>() where T : class
    {
        // Must be at most one constructor
        ConstructorInfo[] constructors = typeof(T).GetConstructors();
        if (constructors.Count() > 1)
            throw new InvalidOperationException($"Unable to create required dependency for {typeof(T).FullName}: type can not have more than one constructor, found {constructors.Count()}");

        // must not be null
        IEnumerable<Type> types = constructors.Single().GetParameters().Select(p => p.ParameterType).ToArray();
        if (Activator.CreateInstance(typeof(T), types.Select(GetService).ToArray()) is T t)
            return t;

        throw new InvalidOperationException($"Unable to create required dependency of type {typeof(T).FullName}: Activator.CreateInstance() returned null");
    }

    private static object GetService(Type type)
    {
        if (Locator.Current.GetService(type) is Object obj)
            return obj;

        throw new InvalidOperationException($"Unable to create required dependency of type {type.FullName}: IReadonlyDependencyResolver.GetService() returned null");
    }
}

我为 Splat 编写了一个官方 NuGet 程序包,它执行构造函数注入。

叫做Splat.DependencyInjection.SourceGenerator。

它使用源代码生成为 class 找到正确的依赖项并将它们注入构造函数。还支持属性注入。

https://github.com/reactivemarbles/Splat.DI.SourceGenerator