สรุป
การใช้เทคนิคที่อธิบายไว้ในคำตอบนี้สามารถใช้บริการ WCF ในบล็อกการใช้ที่มีไวยากรณ์ต่อไปนี้:
var channelFactory = new ChannelFactory<IMyService>("");
var serviceHelper = new ServiceHelper<IMyService>(channelFactory);
var proxy = serviceHelper.CreateChannel();
using (proxy as IDisposable)
{
proxy.DoWork();
}
แน่นอนว่าคุณสามารถปรับให้เข้ากับรูปแบบการเขียนโปรแกรมที่กระชับยิ่งขึ้นเฉพาะกับสถานการณ์ของคุณ - แต่ประเด็นก็คือเราสามารถสร้างการดำเนินการของIMyService
การรีช่องสัญญาณซึ่งใช้รูปแบบการทิ้งอย่างถูกต้อง
รายละเอียด
ทุกคำตอบที่กำหนดป่านนี้แก้ไขปัญหาการรับรอบ "ข้อผิดพลาด" ใน implemention WCF 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
เพื่อปิดช่องของคุณ