[英]Generic constraint based on non-implementation of interface
我有一個帶有工廠服務的應用程序,允許構建實例,同時解決必要的依賴注入。 例如,我使用它來構建對話框視圖模型。 我有一個服務接口,如下所示:
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
方法的假設是有道理的。
我認為除非你強迫開發人員用另一個接口(如ISynchronousInitialize
標記同步方法,否則沒有辦法強制執行“所有不實現IAsyncInitialize
類型應使用Build
方法而不是BuildAsync
”。
您可以嘗試以下方法;
而不必分離方法,只需實現一個具有以下簽名的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; }
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.