I have the following class structure:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.Contracts;
namespace contractsTest
{
class Program
{
static void Main(string[] args)
{
IService s = new Service();
s.sendMessage(MessagesCreator.TestMessage);
}
}
class Service : IService
{
public void DoSomething(Message m)
{
}
}
static class MessageNames
{
public static string TestMessage
{
get
{
Contract.Ensures(!string.IsNullOrWhiteSpace(Contract.Result<string>()));
return "TestMessage";
}
}
}
class Message
{
public Message(string _name)
{
Contract.Requires<ArgumentNullException>(!string.IsNullOrWhiteSpace(_name));
Contract.Ensures(this.Name == _name);
this.Name = _name;
}
public string Name { get; private set; }
}
static class MessagesCreator
{
public static Message TestMessage
{
get
{
Contract.Ensures(Contract.Result<Message>() != null);
Contract.Ensures(Contract.Result<Message>().Name == MessageNames.TestMessage);
return new Message(MessageNames.TestMessage);
}
}
}
static class Extensions
{
public static void sendMessage(this IService service, Message m)
{
Contract.Requires<ArgumentNullException>(service != null);
Contract.Requires<ArgumentNullException>(m != null);
Contract.Requires<ArgumentNullException>(!string.IsNullOrWhiteSpace(m.Name));
service.DoSomething(m);
}
}
[ContractClass(typeof(IServiceContract))]
interface IService
{
void DoSomething(Message m);
}
[ContractClassFor(typeof(IService))]
abstract class IServiceContract : IService
{
public void DoSomething(Message m)
{
Contract.Requires<ArgumentNullException>(m != null);
Contract.Requires<ArgumentException>(!string.IsNullOrWhiteSpace(m.Name));
// Do Something
}
}
}
In Main i get the following Warning CodeContracts: requires unproven: !string.IsNullOrWhiteSpace(m.Name)
Any idea how to fix it?
If I change main to:
static void Main(string[] args)
{
IService s = new Service();
Message messagesCreatorTestMessage = MessagesCreator.TestMessage;
if (string.IsNullOrWhiteSpace(messagesCreatorTestMessage.Name))
throw new InvalidOperationException();
s.sendMessage(messagesCreatorTestMessage);
}
the warning disappears, but there should be other more elegant ways of doing this.
The Ensures in the Message constructor only specifies that the condition will be true when the constructor finishes; it does not indicate that the condition will be true for the life of the Message instance.
To do this, use the Contract.Invariant method:
class Message
{
[ContractInvariantMethod]
private void MessageInvariants()
{
Contract.Invariant(!string.IsNullOrWhiteSpace(Name));
}
public Message(string _name)
{
Contract.Requires<ArgumentNullException>(!string.IsNullOrWhiteSpace(_name));
Contract.Ensures(this.Name == _name);
this.Name = _name;
}
public string Name { get; private set; }
}
It's possible that this is the problem:
// In the Message constructor
Contract.Requires<ArgumentNullException>(!string.IsNullOrWhiteSpace(_componentName));
I suspect you mean:
Contract.Requires<ArgumentNullException>(!string.IsNullOrWhiteSpace(_name));
It's not clear to me where _componentName even comes from...
Related
In this example, I want to patch PatchTarget.QSingleton\<T\>.get_Instance().
How to get it done with Harmony or MonoMod?
Harmony:
"Unhandled exception. System.NotSupportedException: Specified method
is not supported."
MonoMod:
"Unhandled exception. System.ArgumentException: The given generic
instantiation was invalid."
Code snippet: (runnable with dotnetfiddle.net)
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
namespace PatchTarget {
public abstract class QSingleton<T> where T : QSingleton<T>, new() {
protected static T instance = null; protected QSingleton() { }
public static T Instance { get {
if (instance == null) {
instance = new T();
Console.Write($"{typeof(T).Name}.Instance: impl=QSingleton");
}
return instance;
} }
}
}
namespace Patch {
public class TypeHelper<T> where T : PatchTarget.QSingleton<T>, new() {
public static T InstanceHack() {
Console.Write($"{typeof(T).Name}.Instance: impl=InstanceHack");
return null;
}
}
public static class HarmonyPatch {
public static Harmony harmony = new Harmony("Try");
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<>), "InstanceHack");
harmony.Patch(miOriginal, prefix: new HarmonyMethod(miHack));
}
}
public static class MonoModPatch {
public static MonoMod.RuntimeDetour.Detour sHook;
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<>), "InstanceHack");
sHook = new MonoMod.RuntimeDetour.Detour(miOriginal, miHack);
}
}
}
class Program {
public static void Main() {
Patch.HarmonyPatch.init();
// Patch.MonoModPatch.init();
Console.WriteLine($"done");
}
}
After some trial and error, I got something working, but not the reason behind it.
Both Harmony and MonoMod.RuntimeDetour can hook with the typeof(QSingleton<SampleA>).GetMethod(), but not typeof(QSingleton<>).GetMethod().
Harmony output is unexpected.
Harmony attribute annotation doesn't seem to work.
Generating IL seems useless due to the potential lack of TypeSpec for generic.
Questions:
What is the difference between QSingleton<>.Instance and QSingleton<SampleA>.Instance in the sample?
I would guess that <>.Instance is MethodDef, while <SampleA>.Instance is TypeSpec.MemberRef.
Why does Harmony/MonoMod.RuntimeDetour need TypeSpec.MemberRef? For generating redirection stub?
Is it possible to fix the hook under Harmony?
Can Harmony/MonoMod generates "ldtoken <TypeSpec>" if TypeSpec already exists?
Can Harmony/MonoMod dynamically generates necessary TypeSpec for generics?
Code snippet: (runnable with dotnetfiddle.net)
using System;
using HarmonyLib;
namespace PatchTarget {
public abstract class QSingleton<T> where T : QSingleton<T>, new() {
protected static T instance = null; protected QSingleton() { }
public static T Instance { get {
if (instance == null) {
instance = new T();
Console.WriteLine($"{typeof(T).Name}.Instance: impl=QSingleton");
}
return instance;
} }
}
public class SampleA : QSingleton<SampleA> {
public SampleA() { Console.WriteLine("SampleA ctor"); }
}
public class SampleB : QSingleton<SampleB> {
public SampleB() { Console.WriteLine("SampleB ctor"); }
}
}
namespace Patch {
public class TypeHelper<T> where T : PatchTarget.QSingleton<T>, new() {
public static T InstanceHack() {
Console.WriteLine($"{typeof(T).Name}.Instance: impl=InstanceHack");
return null;
}
// For Harmony as Prefix, but attribute does not work.
public static bool InstanceHackPrefix(T __result) {
Console.WriteLine($"{typeof(T).Name}.Instance: impl=InstanceHack");
__result = null;
return false;
}
}
public static class HarmonyPatch {
public static Harmony harmony = new Harmony("Try");
public static void init() {
// Attribute does not work.
// Transpiler does not work because the lack of TypeSpec to setup generic parameters.
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<PatchTarget.SampleB>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<PatchTarget.SampleB>), "InstanceHackPrefix");
harmony.Patch(miOriginal, prefix: new HarmonyMethod(miHack));
}
}
public static class MonoModPatch {
public static MonoMod.RuntimeDetour.Detour sHook;
public static void init() {
var miOriginal = AccessTools.Property(typeof(PatchTarget.QSingleton<PatchTarget.SampleB>), "Instance").GetMethod;
var miHack = AccessTools.Method(typeof(TypeHelper<PatchTarget.SampleB>), "InstanceHack");
sHook = new MonoMod.RuntimeDetour.Detour(miOriginal, miHack);
}
}
}
class Program {
public static void Main() {
_ = PatchTarget.SampleA.Instance;
// MonoMod works (replaces globally).
// Harmony hooks, but in an expected way (T becomes SampleB, not 1st generic type parameter).
// try { Patch.HarmonyPatch.init(); } catch (Exception e) { Console.WriteLine($"Harmony error: {e.ToString()}"); }
try { Patch.MonoModPatch.init(); } catch (Exception e) { Console.WriteLine($"MonoMod error: {e.ToString()}"); }
_ = PatchTarget.SampleB.Instance;
_ = PatchTarget.SampleA.Instance;
Console.WriteLine($"done");
}
}
MonoMod.RuntimeDetour Output:(Work as intended)
SampleA.Instance: impl=QSingleton
SampleB.Instance: impl=InstanceHack
SampleA.Instance: impl=InstanceHack
Harmony Output:(Broken <T>)
SampleA.Instance: impl=QSingleton
SampleB.Instance: impl=InstanceHack
SampleB.Instance: impl=InstanceHack
I've created a class "BankAccount.cs" and am using xunit to test it. I'm testing whether the constructor throws an ArgumentException if it's provided with an empty string as an argument (the argument is the bank account number as a string).
My test fails because no Exception is being thrown - I can't figure out why not.
It appears that the constructor isn't even being called; if I let the constructor throw an Exception (rather than implementing this in the setter of AccountNumber), none is thrown either. Also, if I use Console.WriteLine to debug manually, nothing is written to the console.
Exceptions (Debugging) are set to default settings.
using System;
using System.Collections.Generic;
namespace Banking.Models
{
public class BankAccount : IBankAccount
{
/* NOTE: I've deleted non-relevant fields, properties and methods for brevity */
private string _accountNumber;
public string AccountNumber
{
get { return _accountNumber; }
set
{
if(value == string.Empty)
throw new ArgumentException("Accountnumber must have a value");
_accountNumber = value;
}
}
public BankAccount(string account)
{
AccountNumber = account;
Balance = Decimal.Zero;
_transactions = new List<Transaction>();
}
public override string ToString()
{
return $"{AccountNumber} - {Balance}";
}
public override bool Equals(object obj)
{
BankAccount account = obj as BankAccount;
if (account == null) return false;
return AccountNumber == account.AccountNumber;
}
public override int GetHashCode()
{
return AccountNumber?.GetHashCode() ?? 0;
}
}
}
Tests:
using System;
using Xunit;
using Banking.Models;
namespace Tests.Models
{
public class BankAccountTest : IDisposable
{
// private variables
private BankAccount _account;
private string AccountNumber { get; set; }
// Setup
public BankAccountTest()
{
AccountNumber = "123-4567890-02";
_account = new BankAccount(AccountNumber);
}
[Fact]
public void NewAccount_BalanceZero()
{
Assert.Equal(0, _account.Balance);
}
[Fact]
public void NewAccount_CorrectAccountNumber()
{
Assert.Equal(AccountNumber, _account.AccountNumber);
}
[Fact]
public void NewAccount_EmptyString_Fails()
{
Assert.Throws<ArgumentException>(
() => new BankAccount(string.Empty));
}
// TearDown
public void Dispose()
{
}
}
}
Review the following code, where to take care of cases with both single and named binding for an interface, an abstract factory is used as suggested here
Parameterized Factories Using Ninject
Challenge here is, I need to introduce IEnumerable<T> bankingOperationList, instead of T bankingOperationList, since for named binding it will always use the abstract factory injection, Func<string,T> bankingOperationFunc, but if I don't use IEnumerable<T> suggested above it leads to exception, due to this for even non named single binding, I need to use something like:
bankingOperationList.FirstOrDefault().Withdraw(), even when I know there will only be one dependency.
Another challenge is, for some named bindings it has 30 - 40 bindings in few cases, which will be unnecessarily filled, when I can default T bankingOperationList to null, as it is not required. Please let me know, if the issue needs further clarification. Working Console project underneath.
public interface IBankingOperation
{
void Withdraw();
}
public class BankingOperationOne : IBankingOperation
{
public BankingOperationOne()
{
Console.WriteLine("Testing Constructor :: One :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation One");
}
}
public class BankingOperationTwo : IBankingOperation
{
public BankingOperationTwo()
{
Console.WriteLine("Testing Constructor :: Two :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation Two");
}
}
// Ninject Bindings
public class Bindings : NinjectModule
{
public override void Load()
{
Bind<IBankingOperation>().To<BankingOperationOne>()
.Named("A");
Bind<IBankingOperation>().To<BankingOperationTwo>()
.Named("B");
Bind<Func<string,IBankingOperation>>().ToMethod(ctx => name => ctx.Kernel.Get<IBankingOperation>(name));
}
}
public class BankTran<T> where T : IBankingOperation
{
private IEnumerable<T> bankingOperationList = null;
private Func<string,T> bankingOperationFunc;
public BankTran(IEnumerable<T> boList = null,
Func<string,T> boFunc = null)
{
bankingOperationList = boList;
bankingOperationFunc = boFunc;
}
public void DoOperation(string identifier = null)
{
if(bankingOperationFunc != null)
bankingOperationFunc(identifier).Withdraw();
else
bankingOperationList.FirstOrDefault().Withdraw();
Console.WriteLine("Transaction Successful ");
}
}
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel();
kernel.Load(Assembly.GetExecutingAssembly()); // Load from Bindings (derived from NinjectModule)
var transaction = kernel.Get<BankTran<IBankingOperation>>();
transaction.DoOperation("A");
}
}
Edit 1, based on response by jbl
public interface IBankingOperation<T>
{
void Withdraw();
}
public class BankingOperationOne : IBankingOperation<TestOne>
{
public BankingOperationOne()
{
Console.WriteLine("Testing Constructor :: One :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation One");
}
}
public class BankingOperationTwo : IBankingOperation<TestTwo>
{
public BankingOperationTwo()
{
Console.WriteLine("Testing Constructor :: Two :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation Two");
}
}
public class TestOne { }
public class TestTwo { }
// Ninject Bindings
public class Bindings : NinjectModule
{
public override void Load()
{
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("A");
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("B");
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().WhenInjectedInto(typeof(BankTran<TestOne>));
Bind<Func<string, IBankingOperation<TestOne>>>().ToMethod(ctx => name => ctx.Kernel.Get<IBankingOperation<TestOne>>(name));
Bind<IBankingOperation<TestTwo>>().To<BankingOperationTwo>();
}
}
public class BankTran<T> where T : class
{
private IBankingOperation<T> bankingOperation;
private Func<string, IBankingOperation<T>> bankingOperationFunc;
public BankTran(IBankingOperation<T> bo = null,
Func<string, IBankingOperation<T>> boFunc = null)
{
bankingOperation = bo;
bankingOperationFunc = boFunc;
}
public void DoOperation(string identifier = null)
{
if (bankingOperationFunc != null && identifier != null)
bankingOperationFunc(identifier).Withdraw();
else if (bankingOperation != null)
bankingOperation.Withdraw();
Console.WriteLine("Transaction Successful ");
}
}
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel(new NinjectSettings { AllowNullInjection = true});
kernel.Load(Assembly.GetExecutingAssembly()); // Load from Bindings (derived from NinjectModule)
var transaction = kernel.Get<BankTran<TestOne>>("A"); // Not Working
// var transaction = kernel.Get<BankTran<TestOne>>(); // Working
transaction.DoOperation();
}
}
Assuming BankingOperationOne is your default behaviour,
adding the following line in your Load method should allow to replace the IEnumerable<T> with T in your BankTran constructor :
Bind<IBankingOperation>().To<BankingOperationOne>().WhenInjectedInto(typeof(BankTran<>));
Another solution would be to just define a named binding for default behaviour
Bind<IBankingOperation>().To<BankingOperationOne>().Named("__DefaultBehaviour");
then
public void DoOperation(string identifier = "__DefaultBehaviour")
{
if (bankingOperationFunc != null)
bankingOperationFunc(identifier).Withdraw();
Console.WriteLine("Transaction Successful ");
}
Edit :
You should use the Ninject.Extenstions.Factory nuget package.
Using this package, the following code seems to fullfill you requirements.
public interface IBankingOperation<T>
{
void Withdraw();
}
public interface IBankingOperationFactory<T>
{
IBankingOperation<T> GetBankingOperation(string name);
}
public class BankingOperationOne : IBankingOperation<TestOne>
{
public BankingOperationOne()
{
Console.WriteLine("Testing Constructor :: One :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation One");
}
}
public class BankingOperationTwo : IBankingOperation<TestTwo>
{
public BankingOperationTwo()
{
Console.WriteLine("Testing Constructor :: Two :: Empty");
}
public void Withdraw()
{
Console.WriteLine("Money Withdrawl Operation Two");
}
}
public class TestOne { }
public class TestTwo { }
// Ninject Bindings
public class Bindings : NinjectModule
{
public override void Load()
{
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("A");
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().Named("B");
Bind<IBankingOperation<TestOne>>().To<BankingOperationOne>().WhenInjectedInto(typeof(BankTran<TestOne>));
Bind<IBankingOperationFactory<IBankingOperation<TestOne>>>().ToFactory();
Bind<IBankingOperation<TestTwo>>().To<BankingOperationTwo>();
}
}
public class BankTran<T> where T : class
{
private IBankingOperation<T> bankingOperation;
private IBankingOperationFactory<T> _bankingOperationFactory;
public BankTran(IBankingOperation<T> bo = null,
IBankingOperationFactory<T> bankingOperationFactory = null)
{
bankingOperation = bo;
_bankingOperationFactory = bankingOperationFactory;
}
public void DoOperation(string identifier = null)
{
if (_bankingOperationFactory != null && identifier != null)
_bankingOperationFactory.GetBankingOperation(identifier).Withdraw();
else if (bankingOperation != null)
bankingOperation.Withdraw();
Console.WriteLine("Transaction Successful ");
}
}
class Program
{
static void Main(string[] args)
{
var kernel = new StandardKernel(new NinjectSettings { AllowNullInjection = true });
kernel.Load(Assembly.GetExecutingAssembly()); // Load from Bindings (derived from NinjectModule)
var transaction = kernel.Get<BankTran<TestOne>>();
transaction.DoOperation();
transaction.DoOperation("A");
transaction.DoOperation("B");
}
}
I have a generic delegate like this:
public delegate T SomeHandler<T>(T input);
I have a generic class that take the delegate as a parameter to its constructor like this:
public class SomeClass<T>
{
private SomeHandler<T> m_handler;
public SomeClass(SomeHandler<T> handler)
{
m_handler = handler;
}
public void DoSomeStuff(T input)
{
T result = m_handler(input);
// some stuff
}
}
Most of the time I would instantiate the class with a default handler unless some special case is needed. So I have some default handlers for the types I use:
public static class DefaultHandlers
{
public static string DefaultStringHandler(string input)
{
return input;
}
}
In some cases, the type is instantiated with a special handler that is specific to its implementation:
public class Example
{
private SomeClass<string> m_typicalCase;
private SomeClass<string> m_specialCase;
public Example()
{
m_typicalCase = new SomeClass<string>(DefaultHandlers.DefaultStringHandler);
m_specialCase = new SomeClass<string>(SpecialHandler);
}
private string SpecialHandler(string input)
{
string result;
// Do something special
return result;
}
}
I want to create a default constructor for SomeClass that always instantiates the class with the same default handler for that type, but since the type is not know at compile time, I can't return the delegate that is the right type.
public class SomeClass<T>
{
...
public SomeClass()
{
m_handler = DefaultHandlers.GetDefaultHandler<T>();
}
...
}
Like this
public static class DefaultHandlers
{
public static SomeHandler<T> GetDefaultHandler<T>()
{
if (typeof(T) == typeof(string))
{
return DefaultStringHandler;
}
}
}
This does not work becuase DefaultStringHandler returns a string and the method expects T.
The only way that I have found to do this is the make a type-specific subclass of SomeClass that overloads the default constructor:
public class SomeStringClass : SomeClass<string>
{
public SomeStringClass()
: base(DefaultHandlers.DefaultStringHandler)
{
}
public SomeStringClass(SomeHandler<string> handler)
: base(handler)
{
}
}
It would be fun if generic types could have type-specific overloaded constructors that are used when instantiating the class of a specific type:
public class Foo<T>
{
public Foo<string>(string input)
{
}
public Foo<int>(int input)
{
}
public Foo(T input)
{
}
}
There must be a more elegant way to do with with a design pattern, Strategy maybe?
You could utilize dynamic to get something like SomeClass<string>():
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Zoltan
{
public class SomeClass<T>
{
private static readonly Func<T,T> FALL_BACK_HANDLER = a => a; //or what have you
private readonly Func<T,T> m_handler;
public SomeClass(Func<T,T> handler)
{
m_handler = handler;
}
public SomeClass()
{
m_handler = DefaultHandler.For<T>() ?? FALL_BACK_HANDLER;
}
public void DoSomeStuff(T input)
{
T result = m_handler(input);
Console.WriteLine(result);
}
}
public static class DefaultHandler
{
public static Func<T,T> For<T>()
{
return TypeAware<T>.Default;
}
private static class TypeAware<T>
{
private static readonly Func<T,T> DEFAULT;
static TypeAware()
{
var type = typeof(T);
if (type == typeof(string))
{
DEFAULT = a => DefaultHandler.StringHandler((dynamic) a);
}
else if (type == typeof(int))
{
DEFAULT = a => DefaultHandler.IntHandler((dynamic) a);
}
else
{
DEFAULT = null;
}
}
public static Func<T,T> Default { get { return DEFAULT; } }
}
public static string StringHandler(string a)
{
return a + " The default handler does some stuff!";
}
public static int IntHandler(int a)
{
return a + 2;
}
}
}
You would then consume SomeClass as follows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Zoltan
{
public class Program
{
public static void Main(string[] args)
{
var someStringObj = new SomeClass<string>();
someStringObj.DoSomeStuff("Hello World.");//prints "Hello World. The default handler does some stuff!"
var someIntObj = new SomeClass<int>();
someIntObj.DoSomeStuff(1);//prints 3
var someCustomDoubleObj = new SomeClass<double>(d => d - 2);
someCustomDoubleObj.DoSomeStuff(3);//prints 1
Console.Read();
}
}
}
Building on Jon Skeet and Alexei Levenkovs comments. From what I understand, something like this might be what you're after?
public delegate T SomeHandler<T>(T input);
public class SomeClass<T>
{
private SomeHandler<T> m_handler;
public SomeClass()
{
m_handler = (T input) => input;
}
public SomeClass(SomeHandler<T> handler)
{
m_handler = handler;
}
public void DoSomeStuff(T input)
{
T result = m_handler(input);
// some stuff
}
}
Another way would be to move the string-specific behaviour into a separate class and simply make an instance of that class if you want specific behaviour tied to a specific type
public delegate T SomeHandler<T>(T input);
public class SomeClass<T>
{
protected SomeHandler<T> m_handler;
protected SomeClass()
{
}
public SomeClass(SomeHandler<T> handler)
{
m_handler = handler;
}
public void DoSomeStuff(T input)
{
T result = m_handler(input);
// some stuff
}
}
public class SomeStringClass : SomeClass<string>
{
public SomeStringClass()
{
m_handler = DefaultStringHandler;
}
private string DefaultStringHandler(string input)
{
// Do default string stuff here...
return input;
}
public SomeStringClass(SomeHandler<string> handler):base(handler)
{
}
}
This is how I used to make method calls:
SvcHelper.Using<SomeWebServiceClient>(proxy =>
{
proxy.SomeMethod();
}
public class SvcHelper
{
public static void Using<TClient>(Action<TClient> action) where TClient : ICommunicationObject, IDisposable, new()
{
}
}
This is how I make method calls:
ChannelFactory<ISomethingWebService> cnFactory = new ChannelFactory<ISomethingWebService>("SomethingWebService");
ISomethingWebService client = cnFactory.CreateChannel();
using (new OperationContextScope((IContextChannel)client))
{
client.SomeMethod();
}
My question is: Instead of replacing every instance of my original method call approach; Is there a way to modify my SvcHelper and do the creation of the channel in the SvcHelper constructor and then simply pass the interface like the following:
SvcHelper.Using<ISomethingWebService>(client =>
{
client.SomeMethod();
}
Hope this makes sense and thanks in advance.
First, you don't want to create a new ChannelFactory<T> every call to the Using helper method. They are the most costly thing to construct in the WCF universe. So, at bare minimum, you will want to use a caching approach there.
Second, you don't want to tie yourself to "client" types at all anymore. Just work straight with the service contract interfaces.
Starting from what you've got, here's where I'd go based on how I've done this in the past:
public class SvcHelper
{
private static ConcurrentDictionary<ChannelFactoryCacheKey, ChannelFactory> ChannelFactories = new ConcurrentDictionary<ChannelFactoryCacheKey, ChannelFactory>();
public static void Using<TServiceContract>(Action<TServiceContract> action) where TServiceContract : class
{
SvcHelper.Using<TServiceContract>(action, "*");
}
public static void Using<TServiceContract>(Action<TServiceContract> action, string endpointConfigurationName) where TServiceContract : class
{
ChannelFactoryCacheKey cacheKey = new ChannelFactoryCacheKey(typeof(TServiceContract), endpointConfigurationName);
ChannelFactory<TServiceContract> channelFactory = (ChannelFactory<TServiceContract>)SvcHelper.ChannelFactories.GetOrAdd(
cacheKey,
missingCacheKey => new ChannelFactory<TServiceContract>(missingCacheKey.EndpointConfigurationName));
TServiceContract typedChannel = channelFactory.CreateChannel();
IClientChannel clientChannel = (IClientChannel)typedChannel;
try
{
using(new OperationContextScope((IContextChannel)typedChannel))
{
action(typedChannel);
}
}
finally
{
try
{
clientChannel.Close();
}
catch
{
clientChannel.Abort();
}
}
}
private sealed class ChannelFactoryCacheKey : IEquatable<ChannelFactoryCacheKey>
{
public ChannelFactoryCacheKey(Type channelType, string endpointConfigurationName)
{
this.channelType = channelType;
this.endpointConfigurationName = endpointConfigurationName;
}
private Type channelType;
public Type ChannelType
{
get
{
return this.channelType;
}
}
private string endpointConfigurationName;
public string EndpointConfigurationName
{
get
{
return this.endpointConfigurationName;
}
}
public bool Equals(ChannelFactoryCacheKey compareTo)
{
return object.ReferenceEquals(this, compareTo)
||
(compareTo != null
&&
this.channelType == compareTo.channelType
&&
this.endpointConfigurationName == compareTo.endpointConfigurationName);
}
public override bool Equals(object compareTo)
{
return this.Equals(compareTo as ChannelFactoryCacheKey);
}
public override int GetHashCode()
{
return this.channelType.GetHashCode() ^ this.endpointConfigurationName.GetHashCode();
}
}
}
This should work:
public class SvcHelper
{
public static void Using<TClient>(Action<TClient> action) where TClient : ICommunicationObject, IDisposable
{
ChannelFactory<TClient> cnFactory = new ChannelFactory<TClient>("SomethingWebService");
TClient client = cnFactory.CreateChannel();
using (new OperationContextScope((IContextChannel)client))
{
action(client);
}
}
}