WCF チャネルのヘルパークラス

WCF で クライアント Channel を Close() するとき、非常に稀ですが、ネットワークエラーによって例外が発生する場合があります。

もし例外が発生した場合、次のように書けばクリーンアップできます。

try
{
    // チャネル作成
    NetTcpBinding binding = new NetTcpBinding();
    EndpointAddress address = new EndpointAddress("net.tcp://localhost:8080/Sample");
    ISampleService client = ChannelFactory<ISampleService>.CreateChannel(binding, address);

    // サービス呼び出し
    client.Hoge();

    // チャネルを閉じる
    client.Close();
}
catch (CommunicationException e)
{
    ((IChannel)client).Abort();
}
catch (TimeoutException e)
{
    ((IChannel)client).Abort();
}
catch (Exception e)
{
    ((IChannel)client).Abort();
    throw;
}

でも、このコードを、サービスを呼び出す箇所すべてに記述するのは大変。
次のようなヘルパークラスを用意しておくと便利です。

public class ChannelHelper<TChannel> : IDisposable where TChannel : class
{
    private TChannel _channel;

    public ChannelHelper(TChannel channel)
    {
        if (channel == null)
        {
            throw new ArgumentNullException("channel");
        }
        _channel = channel;
    }

    public TChannel Channel
    {
        get
        {
            if (_channel == null)
            {
                throw new ObjectDisposedException(this.GetType().Name);
            }
            return _channel;
        }
    }

    public void Dispose()
    {
        try
        {
            if (_channel != null)
            {
                if (((IChannel)_channel).State != CommunicationState.Faulted)
                {
                    ((IChannel)_channel).Close();
                }
                else
                {
                    ((IChannel)_channel).Abort();
                }
            }
        }
        catch (CommunicationException)
        {
            ((IChannel)_channel).Abort();
        }
        catch (TimeoutException)
        {
            ((IChannel)_channel).Abort();
        }
        catch (Exception)
        {
            ((IChannel)_channel).Abort();
            throw;
        }
        finally
        {
            _channel = null;
        }
    }
}

Dispose() 内で、内部に保持するチャネルを閉じるようになっています。

このクラスを利用すれば、最初のコードが次のように書けます。

NetTcpBinding binding = new NetTcpBinding();
EndpointAddress address = new EndpointAddress("net.tcp://localhost:8080/Sample");
using(ChannelHelper<ISampleChannel> helper = new ChannelHelper<ISampleChannel>(
    ChannelFactory<ISampleChannel>.CreateChannel(binding, address)))
{
    helper.Channel.Hoge();
}

スッキリ♪

追記

ヘルパークラスに、static なファクトリーメソッドを用意すれば、さらにスッキリします。

public class ChannelHelper<TChannel> : IDisposable where TChannel : class
{
    public static ChannelHelper<TChannel> Create(Binding binding, EndpointAddress address)
    {
        TChannel channel = ChannelFactory<TChannel>.CreateChannel(binding, address);
        return new ChannelHelper(channel);
    }

    ...(省略)...
}

// 使い方
NetTcpBinding binding = new NetTcpBinding();
EndpointAddress address = new EndpointAddress("net.tcp://localhost:8080/Sample");
using(ChannelHelper<ISampleChannel> helper = ChannelHelper<ISampleChannel>.Create(binding, address)))
{
    helper.Channel.Hoge();
}