允许 IEnumerable<T> 延迟解析 MS.DI 中的项目
Allow IEnumerable<T> to resolve items lazily in MS.DI
我的 MS.DI 容器中有一个非常大的 ISample
注册列表,我将其作为 IEnumerable<ISample>
注入。但是在运行时我通常需要很少的。但是,MS.DI 总是立即创建集合的 所有 项,这会导致我的应用程序出现性能问题。
有没有办法允许延迟加载注入的 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.");
}
}
在其他 class 中,我使用服务提供商来创建这些 class 中任何一个的实例。
public ISample GetInstance(Guid Id)
{
return _serviceProvider.GetServices<ISample>().FirstOrDefault(d => d.Id==Id);
}
防止所有项目被预填充的最佳方法是什么?
这取决于真正在 // do some thing
中完成的性质。
如果无论创建的实例如何,这项工作始终相同,则可以使用 static constructor.
初始化一次
否则,您在 ISample
中添加 Initialize
方法的直觉将引导您朝着正确的方向前进。我唯一担心的是,在 GetInstance
中调用此方法不会改变任何性能,因为您只是将相同的工作从构造函数推迟到初始化方法 并且 会引入时间耦合在构造函数和此方法之间。即构造完成后忘记调用此方法,返回的实例将处于corrupted状态(这比之前更糟)。
如果你想完全走下去,你需要问问自己如何将这个复杂的代码从对象的构造(这被认为是 anti-pattern)推迟到后续的方法调用。但是同样,在没有看到构造函数中真正做了什么以及初始化部分在哪里以及如何使用的情况下,我真的不能说什么是如何做的。
为了解决这个问题,我建议使用 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);
我和你class以这种方式注入:
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 构造函数 运行。
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));
根据您的需要,它会变得更复杂,因为上面的例子不支持:
- 注册泛型(添加一个允许为支持流的泛型抽象注册类型的全功能实现会非常麻烦)
- O(n) 使用
.Last()
或 .ElementAt(x)
时的访问权限。这将需要实现返回一个允许 LINQ 扩展方法工作的特殊 ICollection<T>
。这样写起来也会麻烦很多。在这种情况下,切换到支持流式 OOTB 的不同 DI 容器是更好的选择。
请注意,即使使用这种惰性方法,您仍将平均初始化列表的一半(意味着 O(n/2)
的性能)。如果您想要 O(1)
性能,请考虑使用取而代之的是字典。
我的 MS.DI 容器中有一个非常大的 ISample
注册列表,我将其作为 IEnumerable<ISample>
注入。但是在运行时我通常需要很少的。但是,MS.DI 总是立即创建集合的 所有 项,这会导致我的应用程序出现性能问题。
有没有办法允许延迟加载注入的 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.");
}
}
在其他 class 中,我使用服务提供商来创建这些 class 中任何一个的实例。
public ISample GetInstance(Guid Id)
{
return _serviceProvider.GetServices<ISample>().FirstOrDefault(d => d.Id==Id);
}
防止所有项目被预填充的最佳方法是什么?
这取决于真正在 // do some thing
中完成的性质。
如果无论创建的实例如何,这项工作始终相同,则可以使用 static constructor.
初始化一次否则,您在 ISample
中添加 Initialize
方法的直觉将引导您朝着正确的方向前进。我唯一担心的是,在 GetInstance
中调用此方法不会改变任何性能,因为您只是将相同的工作从构造函数推迟到初始化方法 并且 会引入时间耦合在构造函数和此方法之间。即构造完成后忘记调用此方法,返回的实例将处于corrupted状态(这比之前更糟)。
如果你想完全走下去,你需要问问自己如何将这个复杂的代码从对象的构造(这被认为是 anti-pattern)推迟到后续的方法调用。但是同样,在没有看到构造函数中真正做了什么以及初始化部分在哪里以及如何使用的情况下,我真的不能说什么是如何做的。
为了解决这个问题,我建议使用 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);
我和你class以这种方式注入:
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 构造函数 运行。
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));
根据您的需要,它会变得更复杂,因为上面的例子不支持:
- 注册泛型(添加一个允许为支持流的泛型抽象注册类型的全功能实现会非常麻烦)
- O(n) 使用
.Last()
或.ElementAt(x)
时的访问权限。这将需要实现返回一个允许 LINQ 扩展方法工作的特殊ICollection<T>
。这样写起来也会麻烦很多。在这种情况下,切换到支持流式 OOTB 的不同 DI 容器是更好的选择。
请注意,即使使用这种惰性方法,您仍将平均初始化列表的一半(意味着 O(n/2)
的性能)。如果您想要 O(1)
性能,请考虑使用取而代之的是字典。