基于接口未实现的通用约束
本文关键字:实现 约束 于接口 接口 | 更新日期: 2023-09-27 17:57:59
我有一个带有工厂服务的应用程序,可以在解决必要的依赖注入的同时构建实例。例如,我使用它来构建对话框视图模型。我有一个看起来像这样的服务接口:
public interface IAsyncFactory
{
Task<T> Build<T>() where T: class, IAsyncInitialize;
}
理想情况下,我想要的是这样的东西(伪语法,因为这不是直接可以实现的)
public interface IFactory
{
Task<T> Build<T>() where T: class, IAsyncInitialize;
T Build<T>() where T: class, !IAsyncInitialize;
}
这里的想法是,如果一个类支持IAsyncInitialize
,我希望编译器解析为返回Task<T>
的方法,这样从消费代码中可以明显看出,它需要等待初始化。如果该类不支持IAsyncInitialize
,我想直接返回该类。C#语法不允许这样做,但有没有不同的方法来实现我想要的?这里的主要目标是帮助提醒类的使用者实例化它的正确方法,这样对于具有异步初始化组件的类,我就不会在初始化之前尝试使用它。
我能想到的最接近的方法是创建单独的Build
和BuildAsync
方法,如果为IAsyncInitialize
类型调用Build,则会出现运行时错误,但这没有在编译时捕获错误的好处。
通常,Microsoft建议在命名异步方法时添加async
后缀。因此,您假设创建两个名为Build
和BuildAsync
的方法是有意义的。
我认为没有办法强制执行"所有没有实现IAsyncInitialize
的类型都应该使用Build
方法而不是BuildAsync
"之类的东西,除非你强迫开发人员用另一个接口(如ISynchronousInitialize
)标记同步方法。
您可以尝试以下操作;
-
不必分离方法,只需实现一个具有以下签名的
BuildAsync
方法:Task<T> BuildAsync<T>() where T: class
-
在
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<T> Build<T>() where T: class, IAsyncInitialize;
}
此外,我更喜欢避免像InitAsync
这样的专用初始化器方法,而是直接将异步属性公开为任务:
public interface IAsyncData
{
Task<int> AsyncData { get; }
}
// sync version
class SyncClass : IAsyncData
{
readonly Task<int> _data = Task.FromResult(1);
public Task<int> AsyncData
{
get { return _data; }
}
}
// async versions
class AsyncClass : IAsyncData
{
readonly Task<int> _data = GetDataAsync();
public Task<int> AsyncData
{
get { return _data; }
}
private static async Task<int> GetDataAsync()
{
await Task.Delay(1000);
return 1;
}
}
在任何一种情况下,都会对客户端代码施加异步性,即:
var sum = await provider1.AsyncData + await provider2.AsyncData;
然而,我不认为这是一个问题,因为同步版本的Task.FromResult
和await Task.FromResult
的开销非常低。我将发布一些基准。
使用异步属性的方法可以通过Lazy<T>
进一步改进,例如:
public class AsyncData<T>
{
readonly Lazy<Task<T>> _data;
// expose async initializer
public AsyncData(Func<Task<T>> asyncInit, bool makeThreadSafe = true)
{
_data = new Lazy<Task<T>>(asyncInit, makeThreadSafe);
}
// expose sync initializer as async
public AsyncData(Func<T> syncInit, bool makeThreadSafe = true)
{
_data = new Lazy<Task<T>>(() =>
Task.FromResult(syncInit()), makeThreadSafe);
}
public Task<T> AsyncValue
{
get { return _data.Value; }
}
}