基于非实现接口的通用约束

我有一个带有工厂服务的应用程序,允许构建实例,同时解决必要的dependency injection。 例如,我使用它来构建对话框视图模型。 我有一个服务接口,如下所示:

public interface IAsyncFactory { Task Build() where T: class, IAsyncInitialize; } 

理想情况下,我想拥有的是这样的(伪语法,因为这不是直接可实现的)

 public interface IFactory { Task Build() where T: class, IAsyncInitialize; T Build() where T: class, !IAsyncInitialize; } 

这里的想法是,如果一个类支持IAsyncInitialize ,我希望编译器解析为返回Task的方法,以便从消费代码中明显看出它需要等待初始化。 如果该类不支持IAsyncInitialize ,我想直接返回该类。 C#语法不允许这样做,但是有不同的方法来实现我的目标吗? 这里的主要目标是帮助提醒消费者类的实例化它的正确方法,这样对于具有异步初始化组件的类,我不会在初始化之前尝试使用它。

我能想到的最接近的是创建单独的BuildBuildAsync方法,如果为IAsyncInitialize类型调用Build,则会出现运行时错误,但这没有在编译时捕获错误的好处。

通常,Microsoft建议在命名异步方法时添加async后缀。 因此,您创建两个名为BuildBuildAsync方法的假设是有道理的。

我认为除非你强迫开发人员用另一个接口(如ISynchronousInitialize标记同步方法,否则没有办法强制执行“所有不实现IAsyncInitialize类型将使用Build方法而不是BuildAsync ”。

您可以尝试以下方法;

  1. 而不必分离方法,只需实现一个具有以下签名的BuildAsync方法:

     Task BuildAsync() where T: class 
  2. BuildAsync方法中,检查T是否实现IAsyncInitialize 。 如果是这种情况,只需在创建T类型的对象后调用相关的初始化代码。 否则,只需创建一个TaskCompletionSource对象并运行同步初始化代码,就好像它是异步的一样。

以下方法可能不是最好的,但我发现它非常方便。 当异步和同步初始化器都可用(或者可能可用)时,我将同步一个封装为与Task.FromResult异步,并且只将异步方法暴露给客户端:

 public interface IAsyncInitialize { Task InitAsync(); int Data { get; } } // sync version class SyncClass : IAsyncInitialize { readonly int _data = 1; public Task InitAsync() { return Task.FromResult(true); } public int Data { get { return _data; } } } // async version class AsyncClass: IAsyncInitialize { int? _data; public async Task InitAsync() { await Task.Delay(1000); _data = 1; } public int Data { get { if (!_data.HasValue) throw new ApplicationException("Data uninitalized."); return _data.Value; } } } 

这只留下了工厂的异步版本:

 public interface IAsyncFactory { // Build can create either SyncClass or AsyncClass Task Build() where T: class, IAsyncInitialize; } 

此外, 我更喜欢避免使用InitAsync这样的专用初始化方法 ,而是直接将异步属性作为任务公开:

 public interface IAsyncData { Task AsyncData { get; } } // sync version class SyncClass : IAsyncData { readonly Task _data = Task.FromResult(1); public Task AsyncData { get { return _data; } } } // async versions class AsyncClass : IAsyncData { readonly Task _data = GetDataAsync(); public Task AsyncData { get { return _data; } } private static async Task GetDataAsync() { await Task.Delay(1000); return 1; } } 

在任何一种情况下, 它总是在客户端代码上强加异步 ,即:

 var sum = await provider1.AsyncData + await provider2.AsyncData; 

但是,我不认为这是一个问题,因为同步版本的Task.FromResultawait Task.FromResult的开销非常低。 我要发布一些基准测试。

使用Lazy可以进一步改进使用异步属性的方法,例如:

 public class AsyncData { readonly Lazy> _data; // expose async initializer public AsyncData(Func> asyncInit, bool makeThreadSafe = true) { _data = new Lazy>(asyncInit, makeThreadSafe); } // expose sync initializer as async public AsyncData(Func syncInit, bool makeThreadSafe = true) { _data = new Lazy>(() => Task.FromResult(syncInit()), makeThreadSafe); } public Task AsyncValue { get { return _data.Value; } } }