Résumé
Mit den in dieser Antwort beschriebenen Techniken kann man einen WCF-Dienst in einem using-Block mit der folgenden Syntax konsumieren:
var channelFactory = new ChannelFactory<IMyService>("");
var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
Sie können dies natürlich noch weiter anpassen, um ein prägnanteres, auf Ihre Situation zugeschnittenes Programmiermodell zu erhalten - aber der Punkt ist, dass wir eine Implementierung von IMyService
der den Kanal repräsentiert, der das Wegwerfmuster korrekt umsetzt.
Einzelheiten
Alle Antworten, die bisher gegeben wurden, befassen sich mit dem Problem der Umgehung des "Bugs" in der WCF-Channel-Implementierung von IDisposable
. Die Antwort, die das prägnanteste Programmiermodell zu bieten scheint (das Ihnen die Verwendung der using
Block für die Entsorgung von nicht verwalteten Ressourcen) ist diese - wo der Proxy so modifiziert wird, dass er Folgendes implementiert IDisposable
mit einer fehlerfreien Implementierung. Das Problem bei diesem Ansatz ist die Wartungsfreundlichkeit - wir müssen diese Funktionalität für jeden Proxy, den wir verwenden, neu implementieren. In einer Variation dieser Antwort werden wir sehen, wie wir die Zusammensetzung und nicht die Vererbung, um diese Technik allgemein anwendbar zu machen.
Erster Versuch
Es scheint verschiedene Implementierungen für die IDisposable
Implementierung, aber um der Argumentation willen werden wir eine Adaption der Implementierung verwenden, die von der derzeit akzeptierte Antwort .
[ServiceContract]
public interface IMyService
{
[OperationContract]
void DoWork();
}
public class ProxyDisposer : IDisposable
{
private IClientChannel _clientChannel;
public ProxyDisposer(IClientChannel clientChannel)
{
_clientChannel = clientChannel;
}
public void Dispose()
{
var success = false;
try
{
_clientChannel.Close();
success = true;
}
finally
{
if (!success)
_clientChannel.Abort();
_clientChannel = null;
}
}
}
public class ProxyWrapper : IMyService, IDisposable
{
private IMyService _proxy;
private IDisposable _proxyDisposer;
public ProxyWrapper(IMyService proxy, IDisposable disposable)
{
_proxy = proxy;
_proxyDisposer = disposable;
}
public void DoWork()
{
_proxy.DoWork();
}
public void Dispose()
{
_proxyDisposer.Dispose();
}
}
Mit den oben genannten Klassen können wir nun Folgendes schreiben
public class ServiceHelper
{
private readonly ChannelFactory<IMyService> _channelFactory;
public ServiceHelper(ChannelFactory<IMyService> channelFactory )
{
_channelFactory = channelFactory;
}
public IMyService CreateChannel()
{
var channel = _channelFactory.CreateChannel();
var channelDisposer = new ProxyDisposer(channel as IClientChannel);
return new ProxyWrapper(channel, channelDisposer);
}
}
Dies ermöglicht es uns, unseren Dienst mit Hilfe des using
Block:
ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
Dies generisch machen
Alles, was wir bisher getan haben, ist eine Neuformulierung Tomas' Lösung . Was diesen Code daran hindert, generisch zu sein, ist die Tatsache, dass ProxyWrapper
Klasse muss für jeden gewünschten Servicevertrag neu implementiert werden. Wir werden uns nun eine Klasse ansehen, die es uns ermöglicht, diesen Typ dynamisch mit IL zu erstellen:
public class ServiceHelper<T>
{
private readonly ChannelFactory<T> _channelFactory;
private static readonly Func<T, IDisposable, T> _channelCreator;
static ServiceHelper()
{
/**
* Create a method that can be used generate the channel.
* This is effectively a compiled verion of new ProxyWrappper(channel, channelDisposer) for our proxy type
* */
var assemblyName = Guid.NewGuid().ToString();
var an = new AssemblyName(assemblyName);
var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName);
var proxyType = CreateProxyType(moduleBuilder, typeof(T), typeof(IDisposable));
var channelCreatorMethod = new DynamicMethod("ChannelFactory", typeof(T),
new[] { typeof(T), typeof(IDisposable) });
var ilGen = channelCreatorMethod.GetILGenerator();
var proxyVariable = ilGen.DeclareLocal(typeof(T));
var disposableVariable = ilGen.DeclareLocal(typeof(IDisposable));
ilGen.Emit(OpCodes.Ldarg, proxyVariable);
ilGen.Emit(OpCodes.Ldarg, disposableVariable);
ilGen.Emit(OpCodes.Newobj, proxyType.GetConstructor(new[] { typeof(T), typeof(IDisposable) }));
ilGen.Emit(OpCodes.Ret);
_channelCreator =
(Func<T, IDisposable, T>)channelCreatorMethod.CreateDelegate(typeof(Func<T, IDisposable, T>));
}
public ServiceHelper(ChannelFactory<T> channelFactory)
{
_channelFactory = channelFactory;
}
public T CreateChannel()
{
var channel = _channelFactory.CreateChannel();
var channelDisposer = new ProxyDisposer(channel as IClientChannel);
return _channelCreator(channel, channelDisposer);
}
/**
* Creates a dynamic type analogous to ProxyWrapper, implementing T and IDisposable.
* This method is actually more generic than this exact scenario.
* */
private static Type CreateProxyType(ModuleBuilder moduleBuilder, params Type[] interfacesToInjectAndImplement)
{
TypeBuilder tb = moduleBuilder.DefineType(Guid.NewGuid().ToString(),
TypeAttributes.Public | TypeAttributes.Class);
var typeFields = interfacesToInjectAndImplement.ToDictionary(tf => tf,
tf => tb.DefineField("_" + tf.Name, tf, FieldAttributes.Private));
#region Constructor
var constructorBuilder = tb.DefineConstructor(
MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName |
MethodAttributes.RTSpecialName,
CallingConventions.Standard,
interfacesToInjectAndImplement);
var il = constructorBuilder.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0]));
for (var i = 1; i <= interfacesToInjectAndImplement.Length; i++)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg, i);
il.Emit(OpCodes.Stfld, typeFields[interfacesToInjectAndImplement[i - 1]]);
}
il.Emit(OpCodes.Ret);
#endregion
#region Add Interface Implementations
foreach (var type in interfacesToInjectAndImplement)
{
tb.AddInterfaceImplementation(type);
}
#endregion
#region Implement Interfaces
foreach (var type in interfacesToInjectAndImplement)
{
foreach (var method in type.GetMethods())
{
var methodBuilder = tb.DefineMethod(method.Name,
MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig |
MethodAttributes.Final | MethodAttributes.NewSlot,
method.ReturnType,
method.GetParameters().Select(p => p.ParameterType).ToArray());
il = methodBuilder.GetILGenerator();
if (method.ReturnType == typeof(void))
{
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, typeFields[type]);
il.Emit(OpCodes.Callvirt, method);
il.Emit(OpCodes.Ret);
}
else
{
il.DeclareLocal(method.ReturnType);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, typeFields[type]);
var methodParameterInfos = method.GetParameters();
for (var i = 0; i < methodParameterInfos.Length; i++)
il.Emit(OpCodes.Ldarg, (i + 1));
il.Emit(OpCodes.Callvirt, method);
il.Emit(OpCodes.Stloc_0);
var defineLabel = il.DefineLabel();
il.Emit(OpCodes.Br_S, defineLabel);
il.MarkLabel(defineLabel);
il.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ret);
}
tb.DefineMethodOverride(methodBuilder, method);
}
}
#endregion
return tb.CreateType();
}
}
Mit unserer neuen Hilfsklasse können wir nun schreiben
var channelFactory = new ChannelFactory<IMyService>("");
var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
Beachten Sie, dass Sie die gleiche Technik (mit leichten Änderungen) auch für automatisch generierte Clients verwenden können, die für ClientBase<>
(anstelle der Verwendung von ChannelFactory<>
), oder wenn Sie eine andere Implementierung von IDisposable
um Ihren Kanal zu schließen.