如何在 Autofac 中的 scope.Resolve<Service<T>>().Method1() 中使用运行时类型参数?

Posted

技术标签:

【中文标题】如何在 Autofac 中的 scope.Resolve<Service<T>>().Method1() 中使用运行时类型参数?【英文标题】:How do I use Runtime Type Arguments in scope.Resolve<Service<T>>().Method1() in Autofac? 【发布时间】:2021-12-05 10:30:09 【问题描述】:

我有如下通用服务:

public interface ISyncedDataService<T> where T : BaseDBModel
    
        Task<int> SaveItem(T item);
        Task<int> InsertAllItems(List<T> items);
        Task<List<T>> GetItems();
        Task<T> GetItem(long primaryKey);
        Task<int> DeleteItem(T item);
    

并且有一堆类 T1, T2, T3, T4.. T20 继承自 BaseDBModel。

我正在使用 Autofac 来调用如下服务:

using var scope = App.Container.BeginLifetimeScope(); //Container is Autofac.IContainer

var items = await scope.Resolve<ISyncedDataService<T1>>().GetItems(); //T1 inherits BaseDBModel

如果类型 T1...T20 仅在运行时已知,我如何调用 GetItems()?目前我正在使用 if-else 链来使其简单快速。但是我很好奇我如何动态地做到这一点(反思?)

编辑: 使用反射时,挑战在于尝试动态获取扩展方法的 MethodInfo,该扩展方法也是通用方法。

【问题讨论】:

不重要,但最好知道这些类型是如何注册的。是否需要您在这里需要一个嵌套范围? (并不是说它对你的问题有影响,只是知道)。 builder.RegisterInstance(GetService(DependencyService.Get>())).AsImplementedInterfaces().SingleInstance(); //对所有 T1 .. T20 执行此操作。并且服务注册为 [assembly: Xamarin.Forms.Dependency(typeof(Project.Droid.Services.SyncedDataService))] [assembly: Xamarin.Forms.Dependency(typeof(Project.Droid.Services.SyncedDataService))] namespace Project.Droid.Services public class SyncedDataService : Services.SyncedDataService where T : Models.BaseDBModel, new() //android 也一样,ios 也一样 注册实例重要吗?如果是这样,DependencyService.Get&lt;ISyncedDataService&lt;T1&gt;&gt; 如何为您解决实例?这是外部 DI 服务吗? 【参考方案1】:

您无法使用反射解析泛型类中的类型参数。

但是要调用特定类中的方法,这个使用反射的答案可能会有所帮助。

How do I use reflection to call a generic method?

如果你只是想将一个泛型接口注册为一个泛型类。

  `builder.RegisterGeneric(typeof(YourGenericClass<>)).As(typeof(YourGenericInterface<>))`

是使用autofac的代码。

【讨论】:

您可能没有注意到,Autofac 中泛型的 .Resolve() 是一种扩展方法(IComponentContext.Resolve),上述方案不适用于这种情况。 为了简化问题,如何使用泛型类型作为其类型参数来调用泛型方法?上述代码中的对象“范围”从扩展 IComponentContext 中获取 .Resolve() 方法。问题并不完全是关于调用泛型类型,而是更侧重于调用泛型方法。 在我给出的链接中查看答案【参考方案2】:

如果您在运行时没有绑定泛型类型,那么您会遇到这种if-else 类型检查模式。我不确定这是否与 Autofac 有很大关系。冒着听起来很明显的风险,您可以通过调用返回对象的非泛型 Resolve 方法来解决此问题。

object foo = componentContext.Resolve(type);

这是通用的。

您可以做的另一件事是将对象转换回ISyncedDataService&lt;BaseDBModel&gt;,但这仅在您的接口是协变的情况下才有效。声明如下:

interface ISyncedDataService<out T> where T : BaseDBModel

// and then
var foo = (ISyncedDataService<BaseDBModel>)componentContext.Resolve(type);

同样,协方差是否对您的界面有意义是您必须决定的事情。


一些注意事项.. 希望您没有在这里使用service locator anti-pattern。还要注意范围的生命周期,您似乎在解决依赖关系的方法中处理了scope。默认情况下,这应该与范围一起终止依赖关系。希望依赖项被注册为更长的寿命。

【讨论】:

.Resolve() 确实有帮助!感谢那。有时我们确实会错过最明显的路径。【参考方案3】:

我解决了如下:

var dbType = typeof(ISyncedDataService<>).MakeGenericType(runtTimeType);
var genericService = scope.Resolve(dbType);
MethodInfo methodInfo = genericService.GetType().GetMethod("GetItems");
var task = (Task)methodInfo.Invoke(genericService, null);
await task.ConfigureAwait(false);
var resultProperty = task.GetType().GetProperty("Result");
var items = resultProperty.GetValue(task);

结果也必须通过反思从任务中解决。

【讨论】:

1.您可以使用dynamic 关键字来简化这些反射调用。例如。 dynamic genericService = scope.Resolve(dbType); Task task = genericService.GetItems(); 等等。与 .Result 属性相同。 2. 更大的问题是,你如何处理最后的items?你仍然只有一个对象。 3. 如果在循环中调用,这些反射调用会变得很昂贵。您的代码闻起来像服务定位器模式。我希望您的scope.Resolve 不会出现在用户代码/核心层中。 这些项目被一种通用方法进一步使用,该方法将它们保存到数据库的脱机副本中。整个例程用于将服务器中的更新表动态同步到移动设备中的离线副本中。 是的,我在应用程序中非常广泛地使用服务定位器模式。我使用一些更简单的覆盖来使性能影响可以忽略不计。但这种模式帮助我在多种情况下保持了礼仪和理智。

以上是关于如何在 Autofac 中的 scope.Resolve<Service<T>>().Method1() 中使用运行时类型参数?的主要内容,如果未能解决你的问题,请参考以下文章

Autofac中的依赖囚禁现象

Autofac和nopcommerce中的Autofac, 还有反射

Autofac

C# - Autofac解析不同类中的新实例

Autofac - ASP.NET Core 中的动作过滤器中的属性注入

如何在 Avalonia.ReactiveUI 中使用 Autofac 作为 DI 容器?