Calling different methods depending on enum value without using switch - c#

I have the following code:
public void ParseNetworkPacket(IAsyncResult iResult)
{
NetworkConnection networkConnection = (NetworkConnection)iResult.AsyncState;
string teste = NetworkPacketType.ToString();
switch (this.NetworkPacketType)
{
case NetworkPacketType.ShotPacket:
break;
case NetworkPacketType.ShotResponsePacket:
break;
case NetworkPacketType.ChatMessagePacket:
break;
default:
break;
}
networkConnection.BeginReadPacket();
}
NetworkPacketType is a enum defined by me.
In the switch, depending on the type of the enum, I would call a different method.
I would like to do that not using switch, because I may have too many enum types.
Is there any other way to do that? Or wih an enum that's the only possible way?

Beside using a map as suggested in the answer linked by Veer, you could also use reflection. If you would name your methods like the enum values for example, it could be done like this:
public void ParseNetworkPacket(IAsyncResult iResult)
{
NetworkConnection networkConnection = (NetworkConnection)iResult.AsyncState;
string teste = NetworkPacketType.ToString();
string methodName = this.NetworkPacketType.ToString();
MethodInfo methodInfo = GetType().GetMethod(methodName,
BindingFlags.Instance | BindingFlags.NonPublic);
methodInfo.Invoke(this, /* your arguments here */);
networkConnection.BeginReadPacket();
}
private void ShotPacket()
{
....
}
But I would not really recommend this approach if not absolutely neccessary. It can be a pain to maintain this, among other things.

Related

Switch based on generic argument type

In C# 7.1 the below is valid code:
object o = new object();
switch (o)
{
case CustomerRequestBase c:
//do something
break;
}
However, I want to use the pattern switch statement in the following scenario:
public T Process<T>(object message, IMessageFormatter messageFormatter)
where T : class, IStandardMessageModel, new()
{
switch (T)
{
case CustomerRequestBase c:
//do something
break;
}
}
The IDE gives me the error "'T' is a type, which is not valid in the given context"
Is there an elegant way to switch on the type of a generic parameter? I get that in my first example you are switching on the object and the second I'd want to switch on the type T. What would be the best approach to do this?
Below are two different classes called Foo and Bar. You can use one instance of any of these classes as a parameter to a function named Process. After all, you can perform pattern matching as shown in the example function. There is a function named Test for the usage example..
public class Foo
{
public string FooMsg { get; set; }
}
public class Bar
{
public string BarMsg { get; set; }
}
public class Example
{
public T Process<T>(T procClass) where T : class
{
switch (typeof(T))
{
case
var cls when cls == typeof(Foo):
{
var temp = (Foo)((object)procClass);
temp.FooMsg = "This is a Foo!";
break;
}
case
var cls when cls == typeof(Bar):
{
var temp = (Bar)((object)procClass);
temp.BarMsg = "This is a Bar!";
break;
}
}
return
procClass;
}
public void Test(string message)
{
Process(new Foo() { FooMsg = message });
Process(new Bar() { BarMsg = message });
}
}
I agree that there are situation when this approach is faster and not so ugly, and also agree that in any case a better solution should be found, but sometimes the trade-off doesn't pay... so here is a solution (C# 9.0)
return typeof(T) switch
{
Type t when t == typeof(CustomerRequestBase) => /*do something*/ ,
_ => throw new Exception("Nothing to do")
};
I'm going to preface by saying that in general I agree with all the commenters that say switching on a generic T is probably not a good idea. In this case I would advice him to stick with identifying the object, casting it, and then passing it to an appropriate handler.
However, I've been writing a custom binary serializer that needs to be fairly efficient and I've discover one case where I feel the kind of switching (or if statement) he's asking for is justified, so here's how I managed it.
public T ProcessAs<T>(parameters)
{
if (typeof(T) == typeof(your_type_here)
{
your_type_here tmp = process(parameters);
return Unsafe.As<your_type_here, T>(ref tmp);
}
else if (typeof(T) == typeof(your_other_type))
{
your_other_type tmp = otherProcess(parameters);
return Unsafe.As<your_other_type, T>(ref tmp);
}
else
{
throw new ArgumentException(appropriate_msg);
}
}
Note that Unsafe.As<T>(T value) can be used if you're dealing with a class instead of a struct.
If we ignore the codesmell discussion as per comments, an easy readable implementation (hack) can look like this:
public T Process<T>(string number)
{
switch (typeof(T).FullName)
{
case "System.Int32":
return (dynamic) int.Parse(number);
case "System.Double":
return (dynamic) double.Parse(number);
default:
throw new ArgumentException($"{typeof(T).FullName} is not supported");
}
}
Even if you are # with your generic constraints, this is probably bound to cause issues unless you are the sole programmer ;)

Abstract Factory or Factory Method for types with different constructor arguments

I have a factory method as given below. Is there a better way to design this so I do not have to use switch statement and achieve open closed principle
public IPolicy CreatePolicy(Context context)
{
IPolicy policy = default(IPolicy);
ISettings settings = _settings.Get(context);
Policy policyType = (Policy) Enum.Parse(typeof(Policy), settings.Policy);
switch (policyType)
{
case Policy.Policy1:
policy = new Policy1(_policy1Service, _logHandler);
break;
case Policy.Policy2:
policy = new Policy2(_policy2Service, _logHandler);
break;
case Policy.Policy3:
policy = new Policy3(_policy1Service, _policy2Service, _logHandler);
break;
}
return policy;
}
Supposed the enum literals are exactly the same as your class names, you could dynamically create the instances based on the enum literal like
public static IPolicy CreatePolicy(Policy policy)
{
//Change this if the namespace of your classes differs
string ns = typeof(Policy).Namespace;
string typeName = ns + "." + policy.ToString();
return (IPolicy)Activator.CreateInstance(Type.GetType(typeName));
}
Passing constructor parameters is also possible by simply adding them like
return (IPolicy)Activator.CreateInstance(Type.GetType(typeName),
_policy1Service, _logHandler);
Since this is very unusual, think about adding an Initialize(...) method to IPolicy to pass these parameters to allow an empty constuctor.

C# Generic T Class TypeOf, is this possible?

I have a class that is generic. Class<T> and depending in the switch statement in the calling code it can be class<int> class<string> class<decimal>
The the method that returns this returns it as an object because the calling code has no idea what it is until after it is set.
Is there a way to do this once I get the object back from the function?
load(object result)
{
Type t = result.GetType().GetGenericArguments()[0];
Class<t> x = (Class<t>) result;
}
Or do I have to set up an a check to check for each type it can be. If int then Class<int>, etc...
EDIT:
Here is what I am trying to do, actual code:
public class ReportResult<TP>
{
public ReportResult()
{
ReportHeaders = new List<ReportHeader>();
ReportViews = new List<IDataAttributeChild<TP>>();
}
public List<ReportHeader> ReportHeaders {get;set;}
public List<IDataAttributeChild<TP>> ReportViews {get;set;}
}
BAL
public object GetReportData(ReportProcedureNameEventArg procedureNameEventArg)
{
object result = null;
switch (procedureNameEventArg.SelectedNode.Class)
{
case ReportClass.Count:
var r = new ReportResult<int>
{
ReportViews = GetCountByReport(procedureNameEventArg),
ReportHeaders = GetReportHeaders(procedureNameEventArg.SelectedNode.ReportViewId)
};
result = r;
break;
case ReportClass.List:
break;
case ReportClass.Date:
var r = new ReportResult<datetime>
{
ReportViews = GetDateSummaryReport(procedureNameEventArg),
ReportHeaders = GetReportHeaders(procedureNameEventArg.SelectedNode.ReportViewId)
};
result = r;
break;
default:
throw new ArgumentOutOfRangeException();
}
return result;
}
The GUI
public void LoadTreeResult(object result)
{
Type t = result.GetType().GetGenericArguments()[0];
ReportResult<?> fff = (ReportResult<?>)result;
dgResult.Columns.Clear();
foreach (var header in result.ReportHeaders)
{
dgResult.Columns.Add(
new DataGridTextColumn
{
Header = header.Header,
Binding = new Binding(header.Binding)
});
}
// This would also be a switch depending on a property coming
// back to now what class to cast to in order to populate the grid.
List<ReportCountByView> d = new List<ReportCountByView>();
foreach (var reportCountByView in result.ReportViews)
{
d.Add((ReportCountByView)reportCountByView);
}
dgResult.ItemsSource = d;
}
This is a layout of the class model in case it might help.
image of layout
Thanks.
if you are going to call the same operation on the instance 'x' after you resolve it you may think about using an interface, this will allow you to define the methods the object performs (and properties) without defining it's type.
your code may then end up looking something like this
public interface IMyInterface
{
void PerformOperation();
}
public class MyGeneric<T> : IMyInterface
{
T Value {get;set;}
MyGeneric(T val)
{
Value = val;
}
void PerformOperation
{
Console.WriteLine("T is {0}", typeof(T));
Console.WriteLine("Value is {0}", Value);
}
}
public void main(string[] args)
{
IMyInterface inst = null;
switch (args[0])
{
case "string":
inst = new MyGeneric("hello");
break;
case "int":
inst = new MyGeneric(7);
break;
case "decimal"
inst = new MyGeneric(18.9M);
break;
}
inst.PerformOperation();
}
What do you want to do with x later? Also, if you can architect it such that x will always be an instance of some baseclass, you could just cast it to the base class.
It depends on how you intend to use the generic types later on. string, int and decimal have no common base type other than object, but they do share some interfaces, so you could possibly choose something like Class<IComparable> if you want to sort them later on. Or you could declare generic constraints if you want to use multiple interfaces:
Class<T> where T : IEquatable<T>, IComparable<T>, IFormattable // and so on...
But what it seems like you're asking for doesn't seem possible. The entire premise of generics is to set the type at design time.
My Opinion
If the result is always being used to invoke same members of the class(es), then can make an interface and implement it to the different classes and get the result as the interface.
This way you will be able to used different classes yet some same members of the interface that are implemented in those classes
I think the best way to do this switch would be to actually implement seperate methods for your different types, and switch at the UI layer.
For example
public ReportResult<int> GetReportDataCount(ReportProcedureNameEventArg procedureNameEventArg)
public ReportResult<DateTime> GetReportDataDate(ReportProcedureNameEventArg procedureNameEventArg)
public void GetReportDataList(ReportProcedureNameEventArg procedureNameEventArg)
The "List" one is the one that really throws me off, because it returns null, otherwise I'd say do something like
public ReportResult<T> GetReportData<T>(ReportProcedureNameEventArg procedureNameEventArg) where T:struct (and then enforce the types programatically).
If you want to try it with dynamics, I think you could make it work that way basically by doing duck typing with the ReportResult, but It might get messy with your List mode in the mix too.

Enum value to string

Does anyone know how to get enum values to string?
example:
private static void PullReviews(string action, HttpContext context)
{
switch (action)
{
case ProductReviewType.Good.ToString():
PullGoodReviews(context);
break;
case ProductReviewType.Bad.ToString():
PullBadReviews(context);
break;
}
}
Edit:
When trying to use ToString(); the compiler complains because the case statement is expecting a constant. I also know that ToString() is is striked out with a line in intellisense
Yes, you can use .ToString() to get a string value for an enum, however you can't use .ToString() in a switch statement. Switch statements need constant expressions, and .ToString() does not evaluate until runtime, so the compiler will throw an error.
To get the behavior you want, with a little change in the approach, you can use enum.Parse() to convert the action string to an enum value, and switch on that enum value instead. As of .NET 4 you can use Enum.TryParse() and do the error checking and handling upfront, rather than in the switch body.
If it were me, I'd parse the string to an enum value and switch on that, rather than switching on the string.
private static void PullReviews(string action, HttpContext context)
{
ProductReviewType review;
//there is an optional boolean flag to specify ignore case
if(!Enum.TryParse(action,out review))
{
//throw bad enum parse
}
switch (review)
{
case ProductReviewType.Good:
PullGoodReviews(context);
break;
case ProductReviewType.Bad:
PullBadReviews(context);
break;
default:
//throw unhandled enum type
}
}
You're going about this backwards. Don't try to use dynamic strings as case labels (you can't), instead parse the string into an enum value:
private static void PullReviews(string action, HttpContext context)
{
// Enum.Parse() may throw if input is invalid, consider TryParse() in .NET 4
ProductReviewType actionType =
(ProductReviewType)Enum.Parse(typeof(ProductReviewType), action);
switch (actionType)
{
case ProductReviewType.Good:
PullGoodReviews(context);
break;
case ProductReviewType.Bad:
PullBadReviews(context);
break;
default: // consider a default case for other possible values...
throw new ArgumentException("action");
}
}
EDIT: In principle, you could just compare to hard-coded strings in your switch statements (see below), but this is the least advisable approach, since it will simply break when you change the values passed in to the method, or the definition of the enum. I'm adding this since it's worth knowing that strings can be used as case labels, so long as they are compile-time literals. Dynamic values can't be used as cases, since the compiler doesn't know about them.
// DON'T DO THIS...PLEASE, FOR YOUR OWN SAKE...
switch (action)
{
case "Good":
PullGoodReviews(context);
break;
case "Bad":
PullBadReviews(context);
break;
}
public enum Color
{
Red
}
var color = Color.Red;
var colorName = Enum.GetName(color.GetType(), color); // Red
Edit: Or perhaps you want..
Enum.Parse(typeof(Color), "Red", true /*ignorecase*/); // Color.Red
There is no TryParse for Enum, so if you expect errors, you have to use try/catch:
try
{
Enum.Parse(typeof(Color), "Red", true /*ignorecase*/);
}
catch( ArgumentException )
{
// no enum found
}
This code is gonna works.
private enum ProductReviewType{good, bad};
private static void PullReviews(string action)
{
string goodAction = Enum.GetName(typeof(ProductReviewType), ProductReviewType.good);
string badAction = Enum.GetName(typeof(ProductReviewType), ProductReviewType.bad);
if (action == goodAction)
{
PullGoodReviews();
}
else if (action == badAction)
{
PullBadReviews();
}
}
public static void PullGoodReviews()
{
Console.WriteLine("GOOD Review!");
}
public static void PullBadReviews()
{
Console.WriteLine("BAD Review...");
}
Since the parsed string is not constant it can not be used by Switch statement.
Compiled in VS2005
You can use another temp variable to save the Type of enum class, this may improve performance.
I'd suggest you go the other way - try to use the actual enum values as much as possible (by converting the string to an enum value as soon as you can), rather than passing strings around your application:
ProductReviewType actionType = (ProductReviewType)Enum.Parse(typeof(ProductReviewType), val);
// You might want to add some error handling here.
PullReviews(actionType);
private static void PullReviews(ProductReviewType action, HttpContext context)
{
switch (action)
{
case ProductReviewType.Good:
PullGoodReviews(context);
break;
case ProductReviewType.Bad:
PullBadReviews(context);
break;
}
}
Note that I've changed your method signature to accept a ProductReviewType argument; this makes it clear what your method actually needs to implement its logic, and fits with my original point that you should try as much as possible not to pass strings around.
This is just for the fun of it, but what if you used a dictionary of delegates? I realize that you have a completely different approach, but dictionary-izing might actually work better for what you're trying to accomplish, especially since it provides a high degree of modularity, as opposed to a mondo-switch construct (although I have to admit, your enum has only 2 members so its a moot issue). Anyways, I just wanted to air a different way to do things...
The dictionary-based approach would kind of look like this:
namespace ConsoleApplication1
{
public enum ProductReviewType
{
Good,
Bad
}
public static class StringToEnumHelper
{
public static ProductReviewType ToProductReviewType(this string target)
{
// just let the framework throw an exception if the parse doesn't work
return (ProductReviewType)Enum.Parse(
typeof(ProductReviewType),
target);
}
}
class Program
{
delegate void ReviewHandler(HttpContext context);
static readonly Dictionary<ProductReviewType, ReviewHandler>
pullReviewOperations =
new Dictionary<ProductReviewType, ReviewHandler>()
{
{ProductReviewType.Good, new ReviewHandler(PullGoodReviews)},
{ProductReviewType.Bad, new ReviewHandler(PullBadReviews)}
};
private static void PullGoodReviews(HttpContext context)
{
// actual logic goes here...
Console.WriteLine("Good");
}
private static void PullBadReviews(HttpContext context)
{
// actual logic goes here...
Console.WriteLine("Bad");
}
private static void PullReviews(string action, HttpContext context)
{
pullReviewOperations[action.ToProductReviewType()](context);
}
static void Main(string[] args)
{
string s = "Good";
pullReviewOperations[s.ToProductReviewType()](null);
s = "Bad";
pullReviewOperations[s.ToProductReviewType()](null);
// pause program execution to review results...
Console.WriteLine("Press enter to exit");
Console.ReadLine();
}
}
}

Better Alternative to Case Statement

I currently have a switch statement that runs around 300 odd lines. I know this is not as giant as it can get, but I'm sure there's a better way to handle this.
The switch statement takes an Enum that is used to determine certain properties that pertain to logging. Right now the problem sets in that it is very easy to leave out an enumeration value and that it will not be given a value as it is not in the switch statement.
Is there an option one can use to ensure that every enumeration is used and given a custom set of values it needs to do its job?
EDIT:
Code sample as requested: (This is simplistic, but shows exactly what I mean. Also an Enumeration would exist with the below values.)
internal void GenerateStatusLog(LogAction ActionToLog)
{
switch (ActionToLog)
{
case LogAction.None:
{
return;
}
case LogAction.LogThis:
{
ActionText = "Logging this Information";
LogText = "Go for it.";
break;
}
}
// .. Do everything else
}
EDIT
I thought this over again, looked around in related questions in SO, and I wrote some code. I created a class named AdvancedSwitch<T>, which allows you to add cases and exposes a method to evaluate a value and lets you specify values that it should check for existence.
This is what I came up with:
public class AdvancedSwitch<T> where T : struct
{
protected Dictionary<T, Action> handlers = new Dictionary<T, Action>();
public void AddHandler(T caseValue, Action action)
{
handlers.Add(caseValue, action);
}
public void RemoveHandler(T caseValue)
{
handlers.Remove(caseValue);
}
public void ExecuteHandler(T actualValue)
{
ExecuteHandler(actualValue, Enumerable.Empty<T>());
}
public void ExecuteHandler(T actualValue, IEnumerable<T> ensureExistence)
{
foreach (var val in ensureExistence)
if (!handlers.ContainsKey(val))
throw new InvalidOperationException("The case " + val.ToString() + " is not handled.");
handlers[actualValue]();
}
}
You can consume the class this way:
public enum TrafficColor { Red, Yellow, Green }
public static void Main()
{
Console.WriteLine("Choose a traffic color: red, yellow, green?");
var color = (TrafficColor)Enum.Parse(typeof(TrafficColor), Console.ReadLine());
var result = string.Empty;
// Creating the "switch"
var mySwitch = new AdvancedSwitch<TrafficColor>();
// Adding a single case
mySwitch.AddHandler(TrafficColor.Green, delegate
{
result = "You may pass.";
});
// Adding multiple cases with the same action
Action redAndYellowDelegate = delegate
{
result = "You may not pass.";
};
mySwitch.AddHandler(TrafficColor.Red, redAndYellowDelegate);
mySwitch.AddHandler(TrafficColor.Yellow, redAndYellowDelegate);
// Evaluating it
mySwitch.ExecuteHandler(color, (TrafficColor[])Enum.GetValues(typeof(TrafficColor)));
Console.WriteLine(result);
}
With the creative use of anonymous delegates, you can easily add new cases to your "switch block". :)
Not that you can also use lambda expressions, and lambda blocks, eg () => { ... } instead of delegate { ... }.
You can easily use this class instead of the long switch blocks.
Original post:
If you use Visual Studio, always create swich statements with the switch code snippet. Type switch press tab twice, and it auto-generates all the possibilities for you.
Then, add a default case to the end which throws an exception, that way when testing your app you will notice that there is an unhandled case, instantly.
I mean something like this:
switch (something)
{
...
case YourEnum.SomeValue:
...
break;
default:
throw new InvalidOperationException("Default case reached.");
}
Well, there's throwing in the default case... There's no edit / compile time construct other than that.
However Strategy, Visitor and other patterns related to them may be appropriate if you choose to do it at run time.
Sample code will help with getting the best answer.
EDIT: Thanks for the sample. I still think it needs a bit of fleshing out as you dont cover whether there are some parameters that only apply to some cases etc.
Action is often used as an alias for the Command pattern and the fact that your Enum is called LogAction signifies that each value carries with it a behavior - be that implied (you stick appropriate code in a case) or explicit (in the specific Command hierarchy class).
Thus it looks to me like a usage of the Command pattern is appropriate (though your sample doesnt prove it) - i.e., have a class (potentially a hierarchy using constructor overloads or any other [set of] factory mechanisms) that keeps the state associated with the request along with the specific behaviour. Then, instead of passing an Enum value, create an appropriate LogCommand instance to the logger, which just invokes it (potentially passing a Log Sink 'receptacle' which the Command can log into). Otherwise you're poking random subsets of parameters in different places.
SEEALSO related posts:
C# - Is there a better alternative than this to ‘switch on type’?
Replace giant switch statement with what?
One possible solution is to use a SortedDictionary:
delegate void EnumHandler (args);
SortedDictionary <Enum, EnumHandler> handlers;
constructor
{
handlers = new SortedDictionary <Enum, EnumHandler> ();
fill in handlers
}
void SomeFunction (Enum enum)
{
EnumHandler handler;
if (handlers.TryGetValue (enum, out handler))
{
handler (args);
}
else
{
// not handled, report an error
}
}
This method does allow you to replace the handlers dynamically. You could also use a List as the value part of the dictionary and have multiple handlers for each enum.
Try to use reflection.
Decorate enum options with attributes that holds associated value and return this value.
Create static class of constants and use reflection for mapping enum-option to constant by name
hope this will help
Some times storing the options in a map is a good solution, you can externalize the configuration to a file too, not sure if it applies to your application.
Long code example here, and the final generic code is a little heavy (EDIT have added an extra example that eliminates the need for the angle brackets at the expense of some final flexibility).
One thing that this solution will give you is good performance - not quite as good as a straightforward switch statement, but each case statement becomes a dictionary lookup and method invocation, so still pretty good. The first call will get a performance penalty, however, due to the use of a static generic that reflects on initialisation.
Create an attribute and generic type as follows:
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DynamicSwitchAttribute : Attribute
{
public DynamicSwitchAttribute(Type enumType, params object[] targets)
{ Targets = new HashSet<object>(targets); EnumType = enumType; }
public Type EnumType { get; private set; }
public HashSet<object> Targets { get; private set; }
}
//this builds a cache of methods for a given TTarget type, with a
//signature equal to TAction,
//keyed by values of the type TEnum. All methods are expected to
//be instance methods.
//this code can easily be modified to support static methods instead.
//what would be nice here is if we could enforce a generic constraint
//on TAction : Delegate, but we can't.
public static class DynamicSwitch<TTarget, TEnum, TAction>
{
//our lookup of actions against enum values.
//note: no lock is required on this as it is built when the static
//class is initialised.
private static Dictionary<TEnum, TAction> _actions =
new Dictionary<TEnum, TAction>();
private static MethodInfo _tActionMethod;
private static MethodInfo TActionMethod
{
get
{
if (_tActionMethod == null)
{
//one criticism of this approach might be that validation exceptions
//will be thrown inside a TypeInitializationException.
_tActionMethod = typeof(TAction).GetMethod("Invoke",
BindingFlags.Instance | BindingFlags.Public);
if (_tActionMethod == null)
throw new ArgumentException(/*elided*/);
//verify that the first parameter type is compatible with our
//TTarget type.
var methodParams = _tActionMethod.GetParameters();
if (methodParams.Length == 0)
throw new ArgumentException(/*elided*/);
//now check that the first parameter is compatible with our type TTarget
if (!methodParams[0].ParameterType.IsAssignableFrom(typeof(TTarget)))
throw new ArgumentException(/*elided*/);
}
return _tActionMethod;
}
}
static DynamicSwitch()
{
//examine the type TTarget to extract all public instance methods
//(you can change this to private instance if need be) which have a
//DynamicSwitchAttribute defined.
//we then project the attributes and the method into an anonymous type
var possibleMatchingMethods =
from method in typeof(TTarget).
GetMethods(BindingFlags.Public | BindingFlags.Instance)
let attributes = method.GetCustomAttributes(
typeof(DynamicSwitchAttribute), true).
Cast<DynamicSwitchAttribute>().ToArray()
where attributes!= null && attributes.Length == 1
&& attributes[0].EnumType.Equals(typeof(TEnum))
select new { Method = method, Attribute = attributes[0] };
//create linq expression parameter expressions for each of the
//delegate type's parameters
//these can be re-used for each of the dynamic methods we generate.
ParameterExpression[] paramExprs = TActionMethod.GetParameters().
Select((pinfo, index) =>
Expression.Parameter(
pinfo.ParameterType, pinfo.Name ?? string.Format("arg{0}"))
).ToArray();
//pre-build an array of these parameter expressions that only
//include the actual parameters
//for the method, and not the 'this' parameter.
ParameterExpression[] realParamExprs = paramExprs.Skip(1).ToArray();
//this has to be generated for each target method.
MethodCallExpression methodCall = null;
foreach (var match in possibleMatchingMethods)
{
if (!MethodMatchesAction(match.Method))
continue;
//right, now we're going to use System.Linq.Expressions to build
//a dynamic expression to invoke this method given an instance of TTarget.
methodCall =
Expression.Call(
Expression.Convert(
paramExprs[0], typeof(TTarget)
),
match.Method, realParamExprs);
TAction dynamicDelegate = Expression.
Lambda<TAction>(methodCall, paramExprs).Compile();
//now we have our method, we simply inject it into the dictionary, using
//all the unique TEnum values (from the attribute) as the keys
foreach (var enumValue in match.Attribute.Targets.OfType<TEnum>())
{
if (_actions.ContainsKey(enumValue))
throw new InvalidOperationException(/*elided*/);
_actions[enumValue] = dynamicDelegate;
}
}
}
private static bool MethodMatchesAction(MethodInfo method)
{
//so we want to check that the target method matches our desired
//delegate type (TAction).
//The way this is done is to fetch the delegate type's Invoke
//method (implicitly invoked when you invoke delegate(args)), and
//then we check the return type and parameters types of that
//against the return type and args of the method we've been passed.
//if the target method's return type is equal to or derived from the
//expected delegate's return type, then all is good.
if (!_tActionMethod.ReturnType.IsAssignableFrom(method.ReturnType))
return false;
//now, the parameter lists of the method will not be equal in length,
//as our delegate explicitly includes the 'this' parameter, whereas
//instance methods do not.
var methodParams = method.GetParameters();
var delegateParams = TActionMethod.GetParameters();
for (int i = 0; i < methodParams.Length; i++)
{
if (!methodParams[i].ParameterType.IsAssignableFrom(
delegateParams[i + 1].ParameterType))
return false;
}
return true;
}
public static TAction Resolve(TEnum value)
{
TAction result;
if (!_actions.TryGetValue(value, out result))
throw new ArgumentException("The value is not mapped");
return result;
}
}
Now do this in a Unit Test:
[TestMethod]
public void TestMethod1()
{
Assert.AreEqual(1,
DynamicSwitch<UnitTest1, Blah, Func<UnitTest1, int>>.
Resolve(Blah.BlahBlah)(this));
Assert.AreEqual(125,
DynamicSwitch<UnitTest1, Blah, Func<UnitTest1, int>>.
Resolve(Blah.Blip)(this));
Assert.AreEqual(125,
DynamicSwitch<UnitTest1, Blah, Func<UnitTest1, int>>.
Resolve(Blah.Bop)(this));
}
public enum Blah
{
BlahBlah,
Bloo,
Blip,
Bup,
Bop
}
[DynamicSwitchAttribute(typeof(Blah), Blah.BlahBlah)]
public int Method()
{
return 1;
}
[DynamicSwitchAttribute(typeof(Blah), Blah.Blip, Blah.Bop)]
public int Method2()
{
return 125;
}
So, given a value of TEnum, and your preferred 'action' type (in your code you would appear to be simply returning nothing and modifying the internal state of the class), you simply consult the DynamicSwitch<> class, ask it to resolve a target method, and then invoke it inline (passing the target object on which the method will be invoked as the first parameter).
I'm not really expecting any votes for this - it's a MAD solution to be honest (it does have the advantage of being able to be applied for any enum type, and even discreet values of type int/float/double, as well as supporting any delegate type) - so perhaps it's a bit of a sledgehammer!
EDIT
Once you have a static generic like this, angle-bracket hell ensues - so we want to try and get rid of them. A lot of the time, this is done by type inference on method parameters etc - but we have a problem here that we can't easily infer a delegate's signature without repeating the method call i.e. (args) => return.
However, you seem to require a method that takes no parameters and returns void, so you can close over this behemoth generic by fixing the delegate type to Action, and throw a fluid API into the mix as well (if that's your kind of thing):
public static class ActionSwitch
{
public class SwitchOn<TEnum>
{
private TEnum Value { get; set; }
internal SwitchOn(TEnum value)
{
Value = value;
}
public class Call<TTarget>{
private TEnum Value { get; set; }
private TTarget Target { get; set; }
internal Call(TEnum value, TTarget target)
{
Value = value;
Target = target;
Invoke();
}
internal void Invoke(){
DynamicSwitch<TTarget, TEnum, Action<TTarget>>.Resolve(Value)(Target);
}
}
public Call<TTarget> On<TTarget>(TTarget target)
{
return new Call<TTarget>(Value, target);
}
}
public static SwitchOn<TEnum> Switch<TEnum>(TEnum onValue)
{
return new SwitchOn<TEnum>(onValue);
}
}
Now add this to the test project:
[TestMethod]
public void TestMethod2()
{
//no longer have any angle brackets
ActionSwitch.Switch(Blah.Bup).On(this);
Assert.IsTrue(_actionMethod1Called);
}
private bool _actionMethod1Called;
[DynamicSwitch(typeof(Blah), Blah.Bup)]
public void ActionMethod1()
{
_actionMethod1Called = true;
}
Only issue with this (apart from the complexity of the solution :) ) is that you'd have to re-build this static wrapper type whenever you want to use a new type of target delegate for a dynamic switch elsewhere. You could generate a generic version based on the Action<...> and Func<...> delegates that incorporates TArg1, TArg(n) and TReturn (if Func<>) - but you'd end up writing a lot more code.
Perhaps I'll turn this into an article on my blog and do all of that - if I get the time!

Categories