簡體   English   中英

基於非實現接口的通用約束

[英]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#語法不允許這樣做,但有沒有不同的方法來實現我的目標? 這里的主要目標是幫助提醒消費者類的實例化它的正確方法,這樣對於具有異步初始化組件的類,我不會在初始化之前嘗試使用它。

我能想到的最接近的是創建單獨的BuildBuildAsync方法,如果為IAsyncInitialize類型調用Build,則會出現運行時錯誤,但這沒有在編譯時捕獲錯誤的好處。

通常,Microsoft建議在命名異步方法時添加async后綴。 因此,您創建兩個名為BuildBuildAsync方法的假設是有道理的。

我認為除非你強迫開發人員用另一個接口(如ISynchronousInitialize標記同步方法,否則沒有辦法強制執行“所有不實現IAsyncInitialize類型應使用Build方法而不是BuildAsync ”。

您可以嘗試以下方法;

  1. 而不必分離方法,只需實現一個具有以下簽名的BuildAsync方法:

     Task<T> BuildAsync<T>() 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<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.FromResultawait 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM