C# 基于非接口实现的泛型约束
我有一个带有工厂服务的应用程序,允许在解决必要的依赖注入的同时构造实例。例如,我使用它来构造对话框视图模型。我有一个如下所示的服务接口:C# 基于非接口实现的泛型约束,c#,.net,async-await,factory,C#,.net,Async Await,Factory,我有一个带有工厂服务的应用程序,允许在解决必要的依赖注入的同时构造实例。例如,我使用它来构造对话框视图模型。我有一个如下所示的服务接口: public interface IAsyncFactory { Task<T> Build<T>() where T: class, IAsyncInitialize; } public class AsyncData<T> { readonly Lazy<Task<T>> _da
public interface IAsyncFactory
{
Task<T> Build<T>() where T: class, IAsyncInitialize;
}
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; }
}
}
公共接口IAsyncFactory
{
Task Build(),其中T:class,IAsyncInitialize;
}
理想情况下,我想要的是这样的东西(伪语法,因为这不是直接可以实现的)
公共接口IFactory
{
Task Build(),其中T:class,IAsyncInitialize;
T Build(),其中T:class,!IAsyncInitialize;
}
这里的想法是,如果类支持IAsyncInitialize
,我希望编译器解析为返回Task
的方法,以便从使用的代码中可以明显看出它需要等待初始化。如果该类不支持IAsyncInitialize
,我希望直接返回该类。C#语法不允许这样做,但是有没有其他方法来实现我想要的?这里的主要目标是帮助提醒类的使用者实例化它的正确方法,以便对于具有异步初始化组件的类,在初始化之前我不会尝试使用它
我能想到的最接近的方法是创建单独的
Build
和BuildAsync
方法,如果为IAsyncInitialize
类型调用Build,则会出现运行时错误,但这并没有在编译时捕获错误的好处。通常,Microsoft建议在命名异步方法时添加async
后缀。因此,您假设创建两个名为Build
和BuildAsync
的方法是有意义的
我认为没有办法强制执行类似“所有没有实现IAsyncInitialize
的类型都应该使用Build
方法而不是BuildAsync
”的东西,除非您强制开发人员使用另一个接口(如ISynchronousInitialize
)来标记同步方法
你可以尝试以下方法
BuildAsync
方法:
Task<T> BuildAsync<T>() where T: class
Task BuildAsync(),其中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;
}
在这两种情况下,它总是对客户端代码实施异步,即:
var sum = await provider1.AsyncData + await provider2.AsyncData;
但是,我不认为这是一个问题,因为任务的开销。FromResult
和等待任务。对于同步版本,FromResult
的开销非常低。我将发布一些基准测试
使用异步属性的方法可以通过Lazy
进一步改进,例如:
public interface IAsyncFactory
{
Task<T> Build<T>() where T: class, IAsyncInitialize;
}
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; }
}
}
公共类异步数据
{
只读惰性数据;
//公开异步初始值设定项
公共异步数据(Func asyninit,bool makeThreadSafe=true)
{
_data=newlazy(异步,makeThreadSafe);
}
//将同步初始值设定项公开为异步
公共异步数据(Func-syncInit,bool-makeThreadSafe=true)
{
_数据=新延迟(()=>
Task.FromResult(syncInit()),使线程安全;
}
公共任务异步值
{
获取{return}data.Value;}
}
}
你不能真正做到这一点,因为你的构建方法只在返回类型上有所不同。这会起作用,但它有一个不幸的副作用,那就是迫使使用者将所有构建操作都当作是异步的,这反过来又迫使他们自己的初始化是异步的,等等。这有利于标准化初始化过程,但当只有一部分类实际需要异步初始化时,这似乎有点沉重。实际上,ISynchronousInitialize
要求可能不是一个好办法。通过实现这两个接口中的一个,这些类基本上通过这个工厂“选择”构造,这对于我的设计是合理的。这也为在初始化过程中允许附加参数提供了一些有趣的可能性,这在执行依赖项注入时总是很棘手的