Clientes de um serviço WCF não podem ser utilizandos dentro de um bloco Using porque eles podem inesperadamente lançar uma exceção. E mesmo que você capture esta exceção, é possível que uma conexão fique aberta. Vamos analisar a história deste problema e algumas soluções propostas.
Os alicerces do gerenciamento de recursos do .NET são a interface IDisposable e o bloco Using. A menos de objetos CLR, o ciclo de vida de tudo no mundo .NET é gerenciado utilizando-se estas ferramentas. Então, a pergunta que vem a mente é como a Microsoft conseguiu fracassar com isso no framework WCF.
O primeiro problema com os clientes de serviços WCF é que os métodos Close/Dispose podem lançar uma exceção. Sendo uma violação clara dos Princípios de Projeto do Framework e dos contratos IDisposable, isso faz com que o método Dispose seja inseguro ao ser chamado de um block Finally.
Pior ainda, há uma chance de que os métodos Close/Dispose deixem uma conexão aberta se o método Abort não for chamado. Se muitas forem deixadas abertas, isso pode levar a aplicação a ter problemas de performance e instabilidades.
Em uma mensagem em um newsgroup que data de 2006, Brian McNamara nos conta a história real por trás da falha de projeto.
A interface ICommunicationObject (da qual ServiceHost, ClientBase, IChannel, IChannelFactory e IChannerListener derivam) sempre teve dois métodos para destruir um objeto: (a) Close e (b) Abort. A semântica sugere que se você quer desativar de modo graceful, chame Close. Do contrário, para desativar de modo ungraceful, chame Abort.
Como conseqüência, o método Close() recebe um Timeout, possui uma versão assíncrona (já que ele pode bloquear a execução do programa) e pode lançar exceções. As exceções documentadas que o método pode lançar são CommunicationException (da qual CommunicationObjectFaultedException é uma subclasse) e TimeoutException.
O método Abort(), por outro lado, não deveria bloquear a execução (ou lançar alguma exceção esperada) e, desta forma, não possui um parâmetro de timeout e nem uma versão assíncrona.
Estes dois conceitos seguiram firmes da introdução do Indigo até hoje. Até aqui, tudo bem.
Na sua encarnação original, ICommunicationObject : IDisposable. Sendo uma interface de marcação, achamos que seria útil notificar os usuários que eles deveriam liberar o objeto o mais rápido possível. Aí que os problemas começaram.
Até o Beta 1, nós tínhamos Dispose() == Abort(). Parte da razão era que o Dispose() deveria fazer o mínimo necessário para realizar a limpeza. Esse foi possivelmente a maior reclamação que ouvimos no Beta 1. Os usuários iriam colocar seus channels em um block using() e qualquer mensagem em cache esperando para ser enviada seria perdida. Transações não receberiam commit, sessões receberiam ACK etc.
Para atender este feedback nós mudamos o comportamento para que Dipose() ~= Close(). Nós sabíamos que lançar exceções causa problemas (algumas delas estão listadas neste tópico do newsgroup), então fizemos que o Dispose tentasse ser esperto. Isto é, se não estivéssemos no estado Opened, nós iríamos internamente chamar o Abort(). Isso sozinho tem seus próprios problemas, o principal sendo que você não pode inferir nada do ponto de vista de confiabilidade sobre o sistema. O Dispose ainda pode lançar exceções, mas ele não irá te notificar _sempre_ que alguma coisa errada aconteceu. Eventualmente tomamos a decisão de que precisávamos tirar o IDisposable do ICommunicationObject. Depois de muita discussão, o IDisposable permaneceu no ServiceHost e no ClientBase. A teoria era que para a maioria dos usuários, não há problemas de um método Dispose lançar exceções, pois eles preferem contar com a conveniência do bloco using() e da indicação que o objeto deve ser liberado o mais rápido possível. Você poderia questionar (e alguns de nós questionamos) que nós também deveríamos ter removido a interface destas duas classes, mas para bem ou para mal, é nesta situação que nós estamos hoje. É uma questão que nunca vai agradar a todos, então nos nossos exemplos do SDK nós precisamos apoiar melhores práticas, que são utilizar o paradigma try { Close } / catch { Abort }.
Alternativas
Steve Smith propôs um extension method chamado CloseConnection. Este método, chamado de um bloco Finally no lugar do Close, encapsula a lógica Close/Abort.
O usuário bog1978 do newsgroup sugere utilizar o suporte a expressões lambda do C# para criar sua próprioa estrutura tipo Using. Este método recebe um novo objeto cliente e um método anônimo com o mesmo código que um bloco Using normal teria.
Por fim, existe a classe WCF Service Proxy Helper do Erwyn Van Der Meer. Os usuários a criam ao invés das classes proxy normais e ela fará a coisa certa ao fechar a conexão. Assim que é criada, ela irá construir o proxy automaticamente, expondo-o através de uma propriedade somente de leitura.