MEF:能否在类构造前有条件地选择导出实例?
我太懂这种重复操作的痛苦了——每个地方都写[ImportMany]再加过滤逻辑,简直是在复制粘贴垃圾代码。其实完全可以把逻辑集中起来,让业务代码只需要一个简单的[Import]就能拿到合适的调度器,下面是具体的实现方案:
第一步:给两个调度器实现添加元数据标记
首先我们定义一个元数据接口,用来区分测试和生产环境的实现,然后给两个实现加上带元数据的导出:
// 定义元数据接口,用来标记调度器类型 public interface ISchedulerProviderMetadata { bool IsTest { get; } } // 生产环境的调度器实现 [Export(typeof(ISchedulerProvider))] [ExportMetadata("IsTest", false)] public class ProductionSchedulerProvider : ISchedulerProvider { // 这里写你的生产环境调度器逻辑,比如用Rx原生的调度器 public IScheduler CurrentThread => Scheduler.CurrentThread; public IScheduler Dispatcher => Scheduler.Dispatcher; // 其他ISchedulerProvider成员的实现... } // 测试环境的调度器实现 [Export(typeof(ISchedulerProvider))] [ExportMetadata("IsTest", true)] public class TestSchedulerProvider : ISchedulerProvider { // 这里写测试用的调度器逻辑,比如用TestScheduler public IScheduler CurrentThread => new TestScheduler(); public IScheduler Dispatcher => new TestScheduler(); // 其他ISchedulerProvider成员的实现... }
第二步:创建一个“自动适配”的默认调度器
我们导出一个统一的ISchedulerProvider实例,它内部会根据当前环境自动选择测试或生产实现,把所有过滤逻辑都集中在这里:
[Export(typeof(ISchedulerProvider))] public class DefaultSchedulerProvider : ISchedulerProvider { private readonly ISchedulerProvider _actualProvider; // 用ImportMany注入所有带元数据的调度器实现 [ImportingConstructor] public DefaultSchedulerProvider([ImportMany] IEnumerable<Lazy<ISchedulerProvider, ISchedulerProviderMetadata>> allProviders) { // 判断当前是否处于测试环境,你可以根据自己的需求修改这个逻辑 bool isTestEnv = CheckIfInTestEnvironment(); // 根据元数据筛选出对应的调度器 _actualProvider = allProviders .First(provider => provider.Metadata.IsTest == isTestEnv) .Value; } // 把ISchedulerProvider的所有方法都委托给实际的调度器实现 public IScheduler CurrentThread => _actualProvider.CurrentThread; public IScheduler Dispatcher => _actualProvider.Dispatcher; public IScheduler Immediate => _actualProvider.Immediate; public IScheduler NewThread => _actualProvider.NewThread; public IScheduler TaskPool => _actualProvider.TaskPool; public IScheduler ThreadPool => _actualProvider.ThreadPool; // 环境判断逻辑,示例:通过环境变量判断 private bool CheckIfInTestEnvironment() { return Environment.GetEnvironmentVariable("APP_ENV")?.Equals("Test", StringComparison.OrdinalIgnoreCase) == true; // 你也可以换成其他判断方式:比如检查测试程序集是否加载、读取配置文件等 } }
第三步:业务代码直接导入即可
现在所有需要用到ISchedulerProvider的地方,只需要一个简单的[Import],完全不用管过滤逻辑:
public class UserDataService { [Import] public ISchedulerProvider SchedulerProvider { get; set; } public void LoadUserData() { // 直接用SchedulerProvider,它会自动适配当前环境 Observable.Timer(TimeSpan.FromSeconds(1), SchedulerProvider.TaskPool) .Subscribe(_ => Console.WriteLine("数据加载完成")); } }
为什么这个方案更好?
- 避免重复代码:所有环境判断和元数据过滤逻辑都集中在
DefaultSchedulerProvider里,业务代码完全不用关心底层细节 - 可维护性高:以后要修改环境判断逻辑,只需要改
CheckIfInTestEnvironment这一个地方 - 符合依赖注入原则:业务代码依赖的还是抽象的
ISchedulerProvider,没有耦合到具体实现
内容的提问来源于stack exchange,提问作者ket
相关产品推荐
相关产品推荐

