सारांश
इस उत्तर में वर्णित तकनीकों का उपयोग करके निम्नलिखित वाक्य रचना के साथ एक ब्लॉक में एक WCF सेवा का उपभोग कर सकते हैं:
var channelFactory = new ChannelFactory<IMyService>("");
var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
आप निश्चित रूप से अपनी स्थिति के लिए और अधिक संक्षिप्त प्रोग्रामिंग मॉडल प्राप्त करने के लिए इसे और भी अनुकूल कर सकते हैं - लेकिन मुद्दा यह है कि हम IMyService
चैनल को फिर से लागू करने का एक कार्यान्वयन बना सकते हैं जो सही ढंग से डिस्पोजेबल पैटर्न को लागू करता है।
विवरण
इस प्रकार दिए गए सभी जवाब डब्ल्यूसीएफ चैनल के कार्यान्वयन में "बग" के आसपास होने की समस्या को दूर करते हैं IDisposable
। इसका उत्तर सबसे संक्षिप्त प्रोग्रामिंग मॉडल (आपको using
अप्रबंधित संसाधनों पर निपटान के लिए ब्लॉक का उपयोग करने की अनुमति देता है) प्रदान करने के लिए लगता है, यह एक है - जहां प्रॉक्सी को IDisposable
बग-मुक्त कार्यान्वयन के साथ संशोधित किया गया है। इस दृष्टिकोण के साथ समस्या स्थिरता है - हमें इस कार्यक्षमता को कभी भी हमारे द्वारा उपयोग किए जाने वाले प्रॉक्सी के लिए फिर से लागू करना होगा। इस उत्तर की भिन्नता पर हम देखेंगे कि हम इस तकनीक को सामान्य बनाने के लिए विरासत के बजाय रचना का उपयोग कैसे कर सकते हैं ।
पहला प्रयास
कार्यान्वयन के लिए विभिन्न कार्यान्वयन प्रतीत होते हैं IDisposable
, लेकिन तर्क के लिए हम वर्तमान में स्वीकृत उत्तर द्वारा उपयोग किए जाने वाले अनुकूलन का उपयोग करेंगे ।
[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();
}
}
उपरोक्त वर्गों के साथ सशस्त्र हम अब लिख सकते हैं
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);
}
}
यह हमें using
ब्लॉक का उपयोग करके हमारी सेवा का उपभोग करने की अनुमति देता है :
ServiceHelper serviceHelper = ...;
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
यह सामान्य बना रहा है
हमने अब तक टॉमस के समाधान में सुधार किया है । इस कोड को जेनेरिक होने से रोकता है यह तथ्य यह है कि ProxyWrapper
वर्ग को हर सेवा अनुबंध के लिए फिर से लागू किया जाना चाहिए जो हम चाहते हैं। अब हम एक ऐसे वर्ग को देखेंगे जो हमें IL का उपयोग करके गतिशील रूप से इस प्रकार का निर्माण करने की अनुमति देता है:
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();
}
}
हमारे नए सहायक वर्ग के साथ अब हम लिख सकते हैं
var channelFactory = new ChannelFactory<IMyService>("");
var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
ध्यान दें कि आप ऑटो-जेनरेट किए गए क्लाइंट के लिए एक ही तकनीक (थोड़े संशोधनों के साथ) ClientBase<>
का उपयोग कर सकते हैं (जो उपयोग करने के बजाय ) के लिए विरासत में मिला है ChannelFactory<>
, या यदि आप IDisposable
अपने चैनल को बंद करने के लिए एक अलग कार्यान्वयन का उपयोग करना चाहते हैं ।