允许 IEnumerable<T> 在 MS.DI 中延迟解析项目

问题描述 投票:0回答:3

我的 MS.DI 容器中有一个非常大的

ISample
注册列表,我将其作为
IEnumerable<ISample>
注入。但在运行时我通常需要很少的。然而,MS.DI 总是立即创建集合的all 项目,这会导致我的应用程序出现性能问题。

有没有办法允许注入的

IEnumerable<T>
的项目被延迟加载,使其充当流,而不是预先填充的数组?

这是我演示问题的示例代码:

services.AddTransient<ISample, SampleA>();
services.AddTransient<ISample, SampleB>();
services.AddTransient<ISample, SampleC>();

public class SampleA : ISample 
{
    public Guid Id  = FirstGuid;
    public SampleA()
    {
        Console.WriteLine("SampleA created.");
    }
}
public class SampleB : ISample 
{
    public Guid Id  = SecondGuid;
    public SampleB()
    {
        Console.WriteLine("SampleB created.");
    }
}
public class SampleC : ISample 
{
    public Guid Id  = ThirdGuid;
    public SampleC()
    {
        Console.WriteLine("SampleC created.");
    }
}

在其他类中,我使用服务提供者来创建任何这些类的实例。

public ISample GetInstance(Guid Id)
{
     return _serviceProvider.GetServices<ISample>().FirstOrDefault(d => d.Id==Id);
}

防止所有项目被预先填充的最佳方法是什么?

c# dependency-injection service-provider
3个回答
2
投票

为了解决这个问题,我建议使用

Dictionary<Guid, Type>
并通过它们的 ID 添加所有实现,这是我尝试过的并且工作正常:

 public interface ISample
    {
        public Guid Id { get; }
    }
    public class SampleA : ISample
    {
        public Guid Id => Guid.Parse("3f30ae05-b88e-4abf-85b5-22e7ce4b639f");
        public SampleA()
        {
            Console.WriteLine("SampleA created.");
        }
    }
    public class SampleB : ISample
    {
        public Guid Id => Guid.Parse("c4a5b853-433b-4889-af41-cb99a8c71c4a");
        public SampleB()
        {
            Console.WriteLine("SampleB created.");
        }
    }
    public class SampleC : ISample
    {
        public Guid Id => Guid.NewGuid();
        public SampleC()
        {
            Console.WriteLine("SampleC created.");
        }
    }

并在您的启动中像这样注册它们:

services.AddTransient<SampleA>();
services.AddTransient<SampleB>();
services.AddTransient<SampleC>();

var dic = new Dictionary<Guid, Type>()
{
 {Guid.Parse("3f30ae05-b88e-4abf-85b5-22e7ce4b639f"), typeof(SampleA)},
 {Guid.Parse("c4a5b853-433b-4889-af41-cb99a8c71c4a"), typeof(SampleB)},
 {Guid.Parse("c4a5b853-433b-4889-af41-cb99a8c71c4a"), typeof(SampleC)},

};

//you will inject this
services.AddScoped<Func<Guid, ISample>>
(provider => (id) => provider.GetService(dic[id]) as ISample);

我在课堂上以这种方式注入它:

public class UseDI
    {
        private readonly Func<Guid, ISample> _funcSample;

        public UseDI(Func<Guid, ISample> funcSample)
        {
            _funcSample= funcSample;
        }


        public ISample GetInstance(Guid Id)
        {
            return _funcSample(Id);
        }
    }

我已经测试了这个

var sampleB=GetInstance(Guid.Parse("c4a5b853-433b-4889-af41-cb99a8c71c4a"))
并且只有SampleB构造函数运行。


2
投票

MS.DI 的默认行为是解析完整的集合。当您调用

GetServices
或注入
IEnumerable<T>
时都会完成此操作。在幕后,可枚举的是一组急切加载的实例。

虽然您可以重构代码以使用某种类型的工厂,但您也可以注册一个真正充当流的枚举。然而,这并不是 MS.DI 开箱即用的支持。但这可以使用以下代码来实现:

public static void AddStream<TService>(
    this IServiceCollection services, params Type[] implementations)
{
    foreach (var implementation in implementations)
    {
        // TODO: Check if implementation implements TService
        services.AddTransient(implementation, implementation);
    }

    services.AddTransient(c =>
        implementations.Select(c.GetRequiredService).OfType<TService>());
}

调用时,此扩展方法将所有提供的实现注册为容器内的瞬态实现,并注册一个在迭代时开始解析实例的

IEnumerable<T>

AddStream

扩展方法可以调用如下:

services.AddStream<ISample>(typeof(SampleA), typeof(SampleB), typeof(SampleC));
根据您的需求,它会变得更加复杂,因为上面的示例不支持:

    注册泛型类型(添加一个功能齐全的实现来允许注册支持流的泛型抽象类型会非常麻烦)
  • 使用
  • .Last()
  • .ElementAt(x)
    时,
    O(n) 次访问。这需要实现返回一个特殊的 
    ICollection<T>
    ,以允许 LINQ 扩展方法工作。这样写起来也会比较麻烦。在这种情况下,切换到支持流式 OOTB 的不同 DI 容器将是更好的选择。
  • 检查强制依赖关系。换句话说,当从根作用域解析任何作用域(或一次性瞬态)组件时,
  • 作用域验证不会捕获。
请注意,即使采用这种懒惰的方法,您仍然会平均初始化列表的一半(意味着

O(n/2)

 的性能。如果您想要 
O(1)
 的性能,请考虑使用字典。


0
投票
这取决于它的性质是否真的在

// do some thing

中完成。

如果无论创建的实例如何,这项工作始终相同,则可以使用

静态构造函数初始化一次。

否则,您在

Initialize

 中添加 
ISample
 方法的直觉将引导您走向正确的方向。我唯一担心的是,在
GetInstance
内部调用此方法不会改变任何性能,因为您只是将相同的工作从构造函数推迟到初始化方法
并且将在构造函数和此方法之间引入时间耦合。也就是说,在构造之后忘记调用此方法,返回的实例将处于 corrupted 状态(这比以前更糟糕)。

如果你想完全走下去,你需要问自己如何将这段复杂的代码从对象的构造(这被认为是

反模式)推迟到后续的方法调用。但同样,在没有看到构造函数中真正做了什么以及在何处以及如何使用初始化部分的情况下,我真的无法说出如何做到这一点。

© www.soinside.com 2019 - 2024. All rights reserved.