C# 似て非なる型に対する共通処理の実装（gRPC Async*Callクラスの例）


# 目的 #

### 4種類の AsyncCall 型に対して透過的に処理を実装したい ###

Google 公式の C# 実装である `Grpc.Core` では、Unary, ClientStreaming, ServerStreaming, DuplexStreaming の4つのメソッドタイプの戻り値は、それぞれ `AsyncUnaryCall<TResponse>`, `AsyncClientStreamingCall<TRequest, TResponse>`, `AsyncServerStreamingCall<TResponse>`, `AsyncDuplexStreaming<TRequest, TResponse>`型になります。

これらの型のメソッドやプロパティの定義は似ていますが、共通の基底型を持ちません。そして sealed クラスです。`IDisposable`以外のインターフェースも実装していません。そのため、汎用処理を実装しようとするとそれぞれの型に対して実装することになり、ソースコードが冗長になりがちです。透過的に処理を実装する手段を考えます。

# 対象 #

* .NET Standard 1.5+ or .NET Framework 4.5+ C# （VB.netでも同様の実装は可能です。）
* Grpc.Core 1.11.0+ （これ以前のバージョンでも実装は可能なはずです。）

# 実装 #

インターフェースを使って透過的な実装を行います。

* 透過的に扱えるようにするためのインターフェース
* インターフェースを実装したラッパークラス
* 簡単にラップするためのメソッド

## 透過的に扱えるようにするためのインターフェース ##

まず、リクエスト送信とレスポンス受信に関する機能をインターフェースとして定義します。

### IAsyncCall インターフェース ###

全てのメソッドタイプに共通する機能を定義します。全ての AsyncCall 型に当てはまります。

```c#:IAsyncCall
    /// <summary>
    /// Provides the functionality of asynchronous call.
    /// </summary>
    public interface IAsyncCall : IDisposable
    {

        /// <summary>
        /// Gets asynchronous access to response headers.
        /// </summary>
        Task<Metadata> ResponseHeadersAsync { get; }

        /// <summary>
        /// Gets the call status if the call has already finished. Throws InvalidOperationException otherwise.
        /// </summary>
        /// <returns></returns>
        Status GetStatus();

        /// <summary>
        /// Gets the call trailing metadata if the call has already finished. Throws InvalidOperationException otherwise.
        /// </summary>
        /// <returns></returns>
        Metadata GetTrailers();

    }
```

### IAsyncRequestStreamingCall&lt;TRequest&gt; インターフェース ###

ストリーム形式でリクエストを送信するのに必要な機能を定義します。`AsyncClientStreamingCall<TRequest, TResponse>`と`AsyncDuplexStreaming<TRequest, TResponse>`に当てはまります。

```c#:IAsyncRequestStreamingCall&lt;TRequest&gt;
    /// <summary>
    /// Provides the functionality to send streaming requests.
    /// </summary>
    /// <typeparam name="TRequest">The type of the request.</typeparam>
    public interface IAsyncRequestStreamingCall<TRequest> : IAsyncCall
    {

        /// <summary>
        /// Gets the async stream to send streaming requests.
        /// </summary>
        IClientStreamWriter<TRequest> RequestStream { get; }

    }
```

### IAsyncResponseCall&lt;TResponse&gt; インターフェース ###

単一のレスポンスを受け取るのに必要な機能を定義します。`AsyncUnaryCall<TResponse>`と`AsyncClientStreamingCall<TRequest, TResponse>`に当てはまります。

```c#:IAsyncResponseCall&lt;TResponse&gt;
    /// <summary>
    /// Provides the functionality to get a response asynchronously.
    /// </summary>
    /// <typeparam name="TResponse">The type of the response.</typeparam>
    public interface IAsyncResponseCall<TResponse> : IAsyncCall
    {

        /// <summary>
        /// Gets asynchronous call result.
        /// </summary>
        Task<TResponse> ResponseAsync { get; }

        /// <summary>
        /// Allows awaiting this object directly.
        /// </summary>
        /// <returns></returns>
        TaskAwaiter<TResponse> GetAwaiter();

    }
```

### IAsyncResponseStreamingCall&lt;TResponse&gt; インターフェース ###

ストリーム形式でレスポンスを受け取るのに必要な機能を定義します。`AsyncServerStreamingCall<TResponse>`と`AsyncDuplexStreamingCall<TRequest, TResponse>`に当てはまります。

```c#:IAsyncResponseStreamingCall&lt;TResponse&gt;
    /// <summary>
    /// Provides the functionality to read streaming responses.
    /// </summary>
    /// <typeparam name="TResponse">The type of the response.</typeparam>
    public interface IAsyncResponseStreamingCall<TResponse> : IAsyncCall
    {

        /// <summary>
        /// Gets the async stream to read streaming responses.
        /// </summary>
        IAsyncStreamReader<TResponse> ResponseStream { get; }

    }
```

## インターフェースを実装したラッパークラス ##

次に、これらのインターフェースを実装し、AsyncCall オブジェクトをラップするラッパークラスを実装します。ラップ対象の AsyncCall オブジェクトを内包して同名のプロパティとメソッドの戻り値を返すだけの実装ですので、ここでの説明は割愛します。

```C#:AsyncUnaryCallWrapper&lt;TResponse&gt;
internal sealed class AsyncUnaryCallWrapper<TResponse>
: IAsyncResponseCall<TResponse>
}
    internal AsyncUnaryCallWrapper(AsyncUnaryCall<TResponse> call)
    {
    }
}
```
```C#:AsyncClientStreamingCallWrapper&lt;TRequest,TResponse&gt;
internal sealed class AsyncClientStreamingCallWrapper<TRequest, TResponse>
: IAsyncRequestStreamingCall<TRequest>, IAsyncResponseCall<TResponse>
}
    internal AsyncClientStreamingCallWrapper(AsyncClientStreamingCall<TRequest, TResponse> call)
    {
    }
}
```
```C#:AsyncServerStreamingCallWrapper&lt;TResponse&gt;
internal sealed class AsyncServerStreamingCallWrapper<TResponse>
: IAsyncResponseStreamingCall<TResponse>
}
    internal AsyncServerStreamingCallWrapper(AsyncServerStreamingCall<TResponse> call)
    {
    }
}
```
```C#:AsyncDuplexStreamingCallWrapper&lt;TRequest,TResponse&gt;
internal sealed class AsyncDuplexStreamingCallWrapper<TRequest, TResponse>
: IAsyncRequestStreamingCall<TRequest>, IAsyncResponseStreamingCall<TResponse>
}
    iinternal AsyncDuplexStreamingCallWrapper(AsyncDuplexStreamingCall<TRequest, TResponse> call)
    {
    }
}
```

## 簡単にラップするためのメソッド ##

各 AsyncCall 型に対する拡張メソッドとして定義すると、アプリケーションコードからの呼び出しが簡単になります。

```C#:AsyncUnaryCall&lt;TResponse&gt;に対する拡張メソッド
/// <summary>
/// Wrap as <see cref="IAsyncResponseCall{TResponse}"/>.
/// </summary>
/// <typeparam name="TResponse">The type of the response.</typeparam>
/// <param name="call"></param>
/// <returns></returns>
public static IAsyncResponseCall<TResponse> AsResponseCall<TResponse>(this AsyncUnaryCall<TResponse> call)
{
    if (call == null) { return null; }
    return new AsyncUnaryCallWrapper<TResponse>(call);
}
```

```C#:AsyncClientStreamingCall&lt;TRequest,TResponse&gt;に対する拡張メソッド
/// <summary>
/// Wrap as <see cref="IAsyncRequestStreamingCall{TRequest}"/>.
/// </summary>
/// <typeparam name="TRequest">The type of the request.</typeparam>
/// <typeparam name="TResponse">The type of the response.</typeparam>
/// <param name="call"></param>
/// <returns></returns>
public static IAsyncRequestStreamingCall<TRequest> AsRequestStreamingCall<TRequest, TResponse>(this AsyncClientStreamingCall<TRequest, TResponse> call)
{
    if (call == null) { return null; }
    return new AsyncClientStreamingCallWrapper<TRequest, TResponse>(call);
}

/// <summary>
/// Wrap as <see cref="IAsyncResponseCall{TResponse}"/>.
/// </summary>
/// <typeparam name="TRequest">The type of the request.</typeparam>
/// <typeparam name="TResponse">The type of the response.</typeparam>
/// <param name="call"></param>
/// <returns></returns>
public static IAsyncResponseCall<TResponse> AsResponseCall<TRequest, TResponse>(this AsyncClientStreamingCall<TRequest, TResponse> call)
{
    if (call == null) { return null; }
    return new AsyncClientStreamingCallWrapper<TRequest, TResponse>(call);
}
```

```C#:AsyncServerStreamingCall&lt;TResponse&gt;に対する拡張メソッド
/// <summary>
/// Wrap as <see cref="IAsyncResponseStreamingCall{TResponse}"/>.
/// </summary>
/// <typeparam name="TResponse">The type of the response.</typeparam>
/// <param name="call"></param>
/// <returns></returns>
public static IAsyncResponseStreamingCall<TResponse> AsResponseStreamingCall<TResponse>(this AsyncServerStreamingCall<TResponse> call)
{
    if (call == null) { return null; }
    return new AsyncServerStreamingCallWrapper<TResponse>(call);
}
```

```C#:AsyncDuplexStreamingCall&lt;TRequest,TResponse&gt;に対する拡張メソッド
/// <summary>
/// Wrap as <see cref="IAsyncRequestStreamingCall{TRequest}"/>.
/// </summary>
/// <typeparam name="TRequest">The type of the request.</typeparam>
/// <typeparam name="TResponse">The type of the response.</typeparam>
/// <param name="call"></param>
/// <returns></returns>
public static IAsyncRequestStreamingCall<TRequest> AsRequestStreamingCall<TRequest, TResponse>(this AsyncDuplexStreamingCall<TRequest, TResponse> call)
{
    if (call == null) { return null; }
    return new AsyncDuplexStreamingCallWrapper<TRequest, TResponse>(call);
}

/// <summary>
/// Wrap as <see cref="IAsyncResponseStreamingCall{TResponse}"/>.
/// </summary>
/// <typeparam name="TRequest">The type of the request.</typeparam>
/// <typeparam name="TResponse">The type of the response.</typeparam>
/// <param name="call"></param>
/// <returns></returns>
public static IAsyncResponseStreamingCall<TResponse> AsResponseStreamingCall<TRequest, TResponse>(this AsyncDuplexStreamingCall<TRequest, TResponse> call)
{
    if (call == null) { return null; }
    return new AsyncDuplexStreamingCallWrapper<TRequest, TResponse>(call);
}
```

# 利用 #

これらのインターフェースを用いて汎用処理を実装します。
次の例は単純に全てのリクエストを送信した後で完了を通知するだけの内容です。ClientStream, DuplexStream の両方から利用できます。
いろいろな送受信アルゴリズムを実装しようとすると例外処理などの汎用処理を局所化したくなります。そのとうなとき、各インターフェースの基底型である IAsyncCall を引数とするメソッドとして実装することができるようになります。

```C#
/// <summary>
/// 全てのリクエストを送信して完了を通知します。
/// </summary>
/// <typeparam name="TRequest">リクエストの型</typeparam>
/// <param name="call">非同期呼び出しオブジェクト</param>
/// <param name="requests">リクエスト</param>
/// <returns></returns>
public Task SendRequestsAndCompleteAsync<TRequest>(IAsyncRequestStreamingCall<TRequest> call, IEnumerable<TRequest> requests)
{
    try
    {
        foreach(TRequest request in requests)
        {
            await call.RequestStream.WriteAsync(request).ConfigureAwait(false);
        }
        await call.RequestStream.CompleteAsync().ConfigureAwait(false);
    }
    catch (Exception ex)
    {
        await HandleExceptionAsync(call, ex).ConfigureAwait(false);
        throw;
    }
}

/// <summary>
/// 指定された例外に対する処理を行います。
/// </summary>
/// <param name="call">非同期呼び出しオブジェクト</param>
/// <param name="ex">例外</param>
/// <returns></returns>
private async Task HandleExceptionAsync(IAsyncCall call, Exception ex)
{
    // 割愛
}

// 呼び出し
AsyncClientStreamingCall<string, string> call1;
AsyncDuplexStreamingCall<string, string> call2;
IEnumerable<string> requests;

await SendRequestsAndCompleteAsync(call1.AsRequestStreamingCall(), requests).ConfigureAwait(false);
await SendRequestsAndCompleteAsync(call2.AsRequestStreamingCall(), requests).ConfigureAwait(false);

// IAsyncRequestStreamingCall<TRequest> の拡張メソッドとして定義すれば次のように呼び出せます
await call1.AsRequestStreamingCall().SendRequestsAndCompleteAsync(requests).ConfigureAwait(false);
await call2.AsRequestStreamingCall().SendRequestsAndCompleteAsync(requests).ConfigureAwait(false);

```
メソッド名が長すぎたかな。

# 参考）ダックタイピング #

C# 4.0 以降では dynamic を使ってダックタイピングができます。今回の4つの AsyncCall クラスは同じ名前のメソッドやプロパティを持っていますので、ダックタイピングによって共通処理を実装することもできます。
私は VB.NET の option strict off の低品質ソースコードに苦しめられたのでこの手のテクニックはあまり使わないようにしていますが、**メリット／デメリットを理解した上で**使う分にはよいのではないかと思います。

[【@IT】連載：C# 4入門 第1回 dynamic型](http://www.atmarkit.co.jp/fdotnet/csharp4/csharp4_01/csharp4_01_02.html)
[【mxProject】VB.net で開発するときは Option Strict On で　←私のブログです](https://mxproj.blogspot.com/2018/01/vb-optionstrict.html)