问题
I have an application with a factory service to allow construction of instances while resolving the necessary dependency injection. For instance, I use this to construct dialog view models. I have a service interface that looks like this:
public interface IAsyncFactory
{
Task<T> Build<T>() where T: class, IAsyncInitialize;
}
Ideally, what I'd like to have is something like this (pseudo-syntax, as this isn't directly achievable)
public interface IFactory
{
Task<T> Build<T>() where T: class, IAsyncInitialize;
T Build<T>() where T: class, !IAsyncInitialize;
}
The idea here is that if a class supports IAsyncInitialize
, I'd like the compiler to resolve to the method that returns Task<T>
so that it's obvious from the consuming code that it needs to wait for initialization. If the class does not support IAsyncInitialize
, I'd like to return the class directly. The C# syntax doesn't allow this, but is there a different way to achieve what I'm after? The main goal here is to help remind the consumer of the class of the correct way to instantiate it, so that for classes with an asynchronous initialization component, I don't try to use it before it has been initialized.
The closest I can think of is to create separate Build
and BuildAsync
methods, with a runtime error if you call Build for an IAsyncInitialize
type, but this doesn't have the benefit of catching errors at compile time.
回答1:
In general Microsoft suggests to add async
suffix when naming asynchronous methods. Thus, your assumption of creating two methods named as Build
and BuildAsync
makes sense.
I think there is no way to enforce something like "all types that do not implement IAsyncInitialize
shall use Build
method instead of BuildAsync
" unless you force the developers to mark synchronous methods with another interface like ISynchronousInitialize
.
You may try the following;
instead of having to separate methods, just implement one
BuildAsync
method which has the following signature:Task<T> BuildAsync<T>() where T: class
In the
BuildAsync
method check whetherT
implementsIAsyncInitialize
. If this is the case, just call related initialization code after creating the object of typeT
. Otherwise, just create aTaskCompletionSource
object and run the synchronous initialization code as if it is asynchronous.
回答2:
The following approach might not be the best, but I find it very convenient. When both asynchronous and synchronous initializers are available (or possibly can be available), I wrap the synchronous one as asynchronous with Task.FromResult
, and only expose the asynchronous method to the client:
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;
}
}
}
This leaves only the asynchronous version of the factory:
public interface IAsyncFactory
{
// Build can create either SyncClass or AsyncClass
Task<T> Build<T>() where T: class, IAsyncInitialize;
}
Furthermore, I prefer to avoid dedicated initializer methods like InitAsync
, and rather expose asynchronous properties directly as tasks:
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;
}
}
In either case, it always imposes asynchrony on the client code, i.e.:
var sum = await provider1.AsyncData + await provider2.AsyncData;
However, I don't think it's an issue as the overhead of Task.FromResult
and await Task.FromResult
for the synchronous version is quite low. I'm going to post some benchmarks.
The approach using asynchronous properties can be further improved with Lazy<T>
, e.g. like this:
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; }
}
}
来源:https://stackoverflow.com/questions/23016286/generic-constraint-based-on-non-implementation-of-interface