Reputation: 14060
I'm trying to implement the visitor pattern for my data structure, which is based on a class hierarchy. In C# you can't switch on types (yet). I was thinking about doing something like this as a replacement:
public MyAlgorithm : Func<IBase, X> {
// default:
public X apply(IBase data) {}
// case param1 is ConcreteSubclass
public X apply(ConcreteSubclass data) {}
// case param1 is ConcreteOtherClass
public X apply(ConcreteOtherClass data) {}
}
And then calling it with late bound dispatch:
public ICanApplyAlgorithmOn {
public void apply(Func<IBase> alg);
public TResult apply<TResult>(Func<IBase,TResult> alg);
public TResult apply<TParam1,TResult>(Func<IBase, TParam1, TResult> alg);
// etc.
}
public abstract Base : ICanApplyAlgorithmOn {
// etc.
public TResult apply(Func<IBase, X> alg) {
// Hopefully I am correct in deducing that this will call the various apply(...) implementations instead of always method(IBase)
dynamic me = this;
return alg(me);
}
// etc.
}
However, this won't work, since MyAlgorithm
can't inherit from the delegate Func<...>
.
The only solution I saw is to define a lot of interfaces of my own, like the following. Is there a better way?
public interface IAlgorithm { public void apply(IBase data); }
public interface IAlgorithm<TReturn> { public TReturn apply(IBase data); }
public interface IAlgorithm<TP1, TReturn> { public TReturn apply(IBase data, TP1 param1); }
// etc.
Upvotes: 1
Views: 1771
Reputation: 29223
dynamic
enables multiple dispatch.If your goal is simply to choose a function based on the runtime type of the argument, then picking one of these two options would suffice - there's no point in combining them.
Here's a solution that uses dynamic
instead of a visitor:
class MyAlgorithm
{
public X Apply(IBase data)
{
try
{
return ApplyImpl((dynamic) data);
}
catch (RuntimeBinderException ex)
{
throw new ArgumentException(
string.Format("{0} is not implemented for type {1}.", typeof (MyAlgorithm).Name, data.GetType().Name),
ex);
}
}
private X ApplyImpl(ConcreteSubclass sub)
{
// Your implementation here.
return null;
}
private X ApplyImpl(ConcreteOtherClass sub)
{
// Your implementation here.
return null;
}
}
You can use it like this:
var algorithm = new MyAlgorithm();
var data = new ConcreteSubclass();
algorithm.Apply(data);
And, alternatively, you can use a delegate like this:
Func<IBase, X> func = algorithm.Apply;
func(data);
Upvotes: 4
Reputation: 112602
You need two interfaces. One for the visitor and one for the visitable classes
public interface IAlgorithmVisitor<X>
{
public X Visit(IBase data);
public X Visit(ConcreteSubclass data);
public X Visit(ConcreteOtherClass data);
}
public interface IAlgorithmVisitable<X>
{
X Accept(IAlgorithmVisitor<X> visitor);
}
An algorithm class can now implement the Accept
method like this:
public X Accept(IAlgorithmVisitor<X> visitor)
{
return visitor.Visit(this);
}
Note that the method overloading mechanism automatically calls the right overload of Visit
according to the current type. The right method overload is resolved at compile time! (No late binding with dynamic required.)
Now, you could iterate over a collection of algorithms like this
IAlgorithmVisitor<int> visitor = new ConcreteAlgorithmVisitor<int>();
foreach (var algorithm in intAlgorithms) {
int result = algorithm.Accept(visitor);
//TODO: do something with result.
}
However, it is unusual to return a result from an Accept
or Visit
method, as it is the visitor’s task to do something useful. It is not the iterator’s or accepting object’s task. This enables you to create visitors that perform quite different things.
Perhaps the Strategy Pattern would better suit your needs than the Visitor Pattern.
Upvotes: 1
Reputation: 10287
It's a bit unclear for me of what you are trying to archive but for fast runtime type binding you can use the following code:
You can use a Dictionary<Type, Func<object, TReturn>>
as the following:
public class AlgorithmClass<TReturn>
{
private Dictionary<Type, Func<object, TReturn>> mMethods;
public AlgorithmClass<TReturn>(Dictionary<Type, Func<object, TReturn>> methods)
{
mMethods = methods
}
public TReturn Invoke(object argument)
{
Type type = argument.GetType();
//This line supports inheritance and co/contra-variance.
//If you want to archive full performance and not support those features you can just use mMethods.TryGetValue(type, out Func<object, TReturn>);
var kvps = mMethods.Where(x => x.Key.IsAssignableFrom(type));
if(!kvp.Any())
{
throw new MissingMethodException("There is no method which can take " + type.Name + " as an argument");
}
if(kvp.Count() > 1)
{
throw new ArgumentException("There is more than one method which can take " + type.Name + " as an argument");
}
return kvp.First().Value(argument);
}
}
Now in your code you can use the class like that:
AlgorithmClass<ReturnType> algorithm = new AlgorithmClass(new Dictionary<Type, Func<object, ReturnType>>
{
{typeof(int), MethodForIntType},
{typeof(string), MethodForStringType},
{typeof(MyClass), MethodForMyClassType}
});
ReturnType MethodForIntType(object anInt)
{
code...
}
ReturnType MethodForStringType(object aString)
{
code...
}
ReturnType MethodForMyClassType(object aMyClass)
{
code...
}
using dynamic
or System.Reflection
's method Binder
at runtime will slow your program (although the usage of object
as an argument requires boxing, unboxing and casting at the beginning of the method).
Upvotes: 0