Best approach for allowing users to define rules in C# - c#

I've been looking into rules engines and such, but I really am not sure where to start. This is more for experimentation, but I'd like to implement something like this for work in the future. Basically, I have an application where a user submits a form and populates a POCO object with several properties. I want the administrator of the application to be able to define rules based on the properties of said object and store them in a relational database. When the form is submitted, I would then make a decision based on the user defined rules. For example, the admin can go into the application and define rules like following:
if (typeID == 4 && request.benchMarkScore < 10) {
request.denied = true;
request.denyReasons.Add("Score too low for this product");
}
Here's my POCO Object example:
class Request
{
public int benchMarkScore { get; set; }
public int typeID { get; set; }
public double rate { get; set; }
public bool isEligable { get; set; }
public bool denied { get; set; }
public List<string> denyReasons { get; set; }
public Dictionary<string, double> adjustments;
}
Granted I know this is an overly simplified example, but I come across many situations where I users could benefit from this functionality in my applications. I'm not looking for a complete solution, but instead an idea of where to start.

There are a number of ways you could go about this. One suggestion would be to leverage reflection itself, and allow admins to apply a rule. I'm going to keep this simple, but a rule would consist of:
A bunch of properties, operands, and values
The reason(s) for denial.
So let's define that. I am going to keep this simple and just handle equality, you can define additional ones:
public enum Operand
{
Equals
}
Now, we can define an interface called IRule. I am defining an interface so that in the future, you could potentially put special, more complicated, rules in.
public interface IRule<TPOCO> where TPOCO : class
{
bool IsValid(TPOCO poco);
}
And now we'll define our Rule class (Note: this doesn't handle indexed properties):
public class PropertyCompareRule : IRule<Request>
{
private sealed class PropertyCompare
{
public string PropertyName {get; set; }
public Operand Operand {get; set; }
public object Value {get; set;}
public string Reason {get; set; }
}
private List<PropertyCompare> _comparers = new List<PropertyCompare>();
public bool IsValid(Request poco)
{
bool isValid = true; // let's be optimistic!
PropertyInfo[] properties = poco.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where((property) => property.GetIndexParameters().Length == 0 && property.CanRead).ToArray();
foreach(var property in properties)
{
foreach(var comparer in _comparers)
{
bool localIsValid;
if(comparer.PropertyName == property.Name)
{
object val = property.GetValue(poco, null);
switch(comparer.Operand)
{
case Operand.Equals:
{
localIsValid = object.Equals(val, property.Value);
break;
}
}
if(!localIsValid)
{
poco.denyReasons.Add(comparer.Reason);
isValid = false;
}
}
}
}
return isValid;
}
public void AddComparer(string propertyName, Operand op, object value, string reason)
{
_comparers.Add(new PropertyCompare() { PropertyName = propertyName, Operand = op, Value = value, Reason = reason });
}
}
It wouldn't be difficult for you to be able to persist the property name, operand, and value details in a database or other such storage. Assuming we fleshed out our enum above, we could conceivably do:
PropertyCompareRule rule = new PropertyCompareRule();
rule.AddComparer("typeID", Operand.Equal, 4, "Reason 1");
rule.AddComparer("benchMarkScore", Operand.LessThan, 10, "Reason 2");
bool valid = rule.IsValid(somePocoInstance);
Edit: Some notes
I use a localIsValid rather than bailing out at the first opportunity. You can change this if you want, but the idea is that it allows a single rule to have multiple points of deniability. This may or may not be what you wish - but it's easy enough to refactor the code so that it bails out the moment a single property comparison fails.
This is a nit-pick, but generally C# style-guidlines dictate properties shouldn't be camel-caps... but that's entirely up to you at the end of the day :)

As I understand you, you are looking for some kind of a scripting system for business rules. I found this blog post where some scripting environment are mentioned.
You can also create assemblies on the fly like mentioned here: https://stackoverflow.com/a/4181855/1229622.

Related

Cannot find how to do equality for the object I am dealing with POCS collection of POCS object

Consider a simple POCS object FeeInfo (yes I know an object would normally be called simply Fee, but the usage of it makes the class name FeeInfo make more sense.)
public FeeInfo()
{
Description = String.Empty;
Status = String.Empty;
}
public string Description { get; set; }
public string Status { get; set; }
public decimal FeeAmount { get; set; }
public decimal FeePaid { get; set; }
public bool Equals(FeeInfo targetFee)
{
if (targetFee.Description == this.Description && targetFee.Status == this.Status &&
targetFee.FeeAmount == this.FeeAmount && targetFee.FeePaid == this.FeePaid)
{
return true;
}
return false;
}
I am clearly not using the interfaces for comparing / equality (largely because I don't yet understand them enough to understand if I need/want them. For this object, my equality method works to my needs.
So imagine another object FeesInfo. Where FeesInfo wraps
private readonly List<FeeInfo> _fees = new List<FeeInfo>();
implements :IEnumerable and the like.
I want to be able to compare two FeesInfo objects to ensure that the FeeInfo objects within it are the same. This would be two instances which contain the same number (Count) of elements which is easy. But I want an unordered comparison of the objects whose FeeInfo properties are the same.
What is the best way to achieve this for this not super savy c# programmer? Thanks
The COMMENT is not an "answer" exactly. But it is THE answer.
First fix your implementation of Equals on your FeeInfo class. You may want to follow this guide: How to define value equality for a class or struct (C# Programming Guide)

Implementing level by level fallback

I have a class ScoreStrategy that describes how to calculate points for a quiz:
public class ScoreStrategy
{
public int Id { get; set; }
public int QuizId { get; set; }
[Required]
public Quiz Quiz { get; set; }
public decimal Correct { get; set; }
public decimal Incorrect { get; set; }
public decimal Unattempted { get; set; }
}
Three properties Correct, Incorrect and Unattempted describe how many points to be assigned for a response. These points can also be negative. The score strategy applies to all questions in the quiz, thus there can only be one ScoreStrategy per quiz.
I have two subclasses:
public class DifficultyScoreStrategy : ScoreStrategy
{
public QuestionDifficulty Difficulty { get; set; }
}
public class QuestionScoreStrategy : ScoreStrategy
{
[Required]
public Question Question { get; set; }
}
My questions have three difficulty levels(Easy, Medium, Hard; QuestionDifficulty is an enum). The DifficultyScoreStrategy specifies if points for questions of a specific difficulty need to be assigned differently. This overrides the base ScoreStrategy that applies to the entire quiz. There can be one instance per difficulty level.
Thirdly, I have a QuestionScoreStrategy class that specifies if points for a specific question have to be awarded differently. This overrides both the quiz-wide ScoreStrategy and the difficulty-wide DifficultyStrategy. There can be one instance per question.
While evaluating the responses of the quiz, I want to implement a level-by-level fallback mechanism:
For each question:
Check if there is a QuestionScoreStrategy for the question and return the strategy if one is found.
If not, fallback to DifficultyScoreStrategy and check if there is a strategy for the difficulty level of the question being evaluated
and return it if a strategy is found.
If not, fallback to the quiz-wide ScoreStrategy and check if one exists and return it if it does,
If there is no ScoreStrategy either, use default as { Correct = 1, Incorrect = 0, Unattempted = 0 }(It would be great if I can make this configurable as well, something much like the .NET's elegant way:
options => {
options.UseFallbackStrategy(
correct: 1,
incorrect: 0,
unattempted: 0
);
}
).
Summary
I've summarized the above info in a table:
Strategy Type
Priority
Maximum instances per quiz
QuestionScoreStrategy
1st (highest)
As many as there are questions in the quiz
DifficultyScoreStrategy
2nd
4, one for each difficulty level
ScoreStrategy
3rd
Only one
Fallback strategy (Default { Correct = 1, Incorrect = 0, Unattempted = 0})
4th (lowest)
Configured for the entire app. Shared by all quizzes
I have a container class called EvaluationStrategy that holds these score strategies among other evaluation info:
partial class EvaluationStrategy
{
public int Id { get; set; }
public int QuizId { get; set; }
public decimal MaxScore { get; set; }
public decimal PassingScore { get; get; }
public IEnumerable<ScoreStrategy> ScoreStrategies { get; set; }
}
What I have tried:
I have added a method called GetStrategyByQuestion() to the same EvaluationStrategy class above(note it is declared as partial) that implements this fallback behavior and also a companion indexer that in turn calls this method. I have declared two HashSets of types DifficultyScoreStrategy and QuestionScoreStrategy and an Initialize() method instantiates them. All the score strategies are then switched by type and added to the appropriate HashSet, there can only be one ScoreStrategy per quiz, which will be stored in defaultStrategy:
partial class EvaluationStrategy
{
private ScoreStrategy FallbackStrategy = new() { Correct = 1, Incorrect = 0, Unattempted = 0 };
private ScoreStrategy defaultStrategy;
HashSet<DifficultyScoreStrategy> dStrategies;
HashSet<QuestionScoreStrategy> qStrategies;
public void Initialize()
{
qStrategies = new();
dStrategies = new();
// Group strategies by type
foreach (var strategy in strategies)
{
switch (strategy)
{
case QuestionScoreStrategy qs: qStrategies.Add(qs); break;
case DifficultyScoreStrategy ds: dStrategies.Add(ds); break;
case ScoreStrategy s: defaultStrategy = s; break;
}
}
}
public ScoreStrategy this[Question question] => GetStrategyByQuestion(question);
public ScoreStrategy GetStrategyByQuestion(Question question)
{
if (qStrategies is null || dStrategies is null)
{
Initialize();
}
// Check if question strategy exists
if (qStrategies.FirstOrDefault(str => str.Question.Id == question.Id) is not null and var qs)
{
return qs;
}
// Check if difficulty strategy exists
if (dStrategies.FirstOrDefault(str => str.Question.Difficulty == question.Difficulty) is not null and var ds)
{
return ds;
}
// Check if default strategy exists
if (defaultStrategy is not null)
{
return defaultStrategy;
}
// Fallback
return FallbackStrategy;
}
}
This method seems a bit clumsy and doesn't quite feel right to me. Using a partial class and adding to EvalutationStrategy doesn't seem right either. How do I implement this level-by-level fallback behavior? Is there a design pattern/principle I can use here? I know many things in the .NET framework fallback to default conventions if not configured. I need something similar. Or can someone simply recommend a cleaner and elegant solution with better maintainability?
NOTE/ADDITIONAL INFO: The ScoreStrategys and EvaluationStrategy for all quizzes are stored in a database managed by EF Core(.NET 5) with TPH mapping:
modelBuilder.Entity<ScoreStrategy>()
.ToTable("ScoreStrategy")
.HasDiscriminator<int>("StrategyType")
.HasValue<ScoreStrategy>(0)
.HasValue<DifficultyScoreStrategy>(1)
.HasValue<QuestionScoreStrategy>(2)
;
modelBuilder.Entity<EvaluationStrategy>().ToTable("EvaluationStrategy");
I have a single base DbSet<ScoreStrategy> ScoreStrategies and another DbSet<EvaluationStrategy> EvaluationStrategies. Since EvaluationStrategy is an EF Core class, I'm a bit skeptical about adding logic(GetStrategyByQuestion()) to it as well.
With Polly
There is a 3rd party library called Polly which defines a policy called Fallback.
With this approach you can easily define a fallback chain like this:
public ScoreStrategy GetStrategyByQuestionWithPolly(Question question)
{
Func<ScoreStrategy, bool> notFound = strategy => strategy is null;
var lastFallback = Policy<ScoreStrategy>
.HandleResult(notFound)
.Fallback(FallbackStrategy);
var defaultFallback = Policy<ScoreStrategy>
.HandleResult(notFound)
.Fallback(defaultStrategy);
var difficultyFallback = Policy<ScoreStrategy>
.HandleResult(notFound)
.Fallback(() => GetApplicableDifficultyScoreStrategy(question));
var fallbackChain = Policy.Wrap(lastFallback, defaultFallback, difficultyFallback);
fallbackChain.Execute(() => GetApplicableQuestionScoreStrategy(question));
}
I've extracted the strategy selection logic for QuestionScoreStrategy and DifficultyScoreStrategy like this:
private ScoreStrategy GetApplicableQuestionScoreStrategy(Question question)
=> qStrategies.FirstOrDefault(str => str.Question.Id == question.Id);
private ScoreStrategy GetApplicableDifficultyScoreStrategy(Question question)
=> dStrategies.FirstOrDefault(str => str.Difficulty == question.Difficulty);
Pros
There is a single return statement
The policy declarations are separated from chaining
Each and every fallback can be triggered by different conditions
Primary selection logic is separated from the fallbacks
Cons
The code is really repetitive
You can't create a fallback chain by utilizing a fluent API
You need to use a 3rd party library
Without Polly
If you don't want to use a 3rd party library just to define and use a fallback chain you do something like this:
public ScoreStrategy GetStrategyBasedOnQuestion(Question question)
{
var fallbackChain = new List<Func<ScoreStrategy>>
{
() => GetApplicableQuestionScoreStrategy(question),
() => GetApplicableDifficultyScoreStrategy(question),
() => defaultStrategy,
() => FallbackStrategy
};
ScoreStrategy selectedStrategy = null;
foreach (var strategySelector in fallbackChain)
{
selectedStrategy = strategySelector();
if (selectedStrategy is not null)
break;
}
return selectedStrategy;
}
Pros
There is a single return statement
The fallback chain declaration and evaluation are separated
It is simple and concise
Cons
It is less flexible: each fallback selection is triggered by the same condition
Primary selection is not separated from fallbacks
You can sort the sequence of ScoringMethods by your priority.
First you sort by whether str is QuestionScoreStrategy and str.Question.Id == question.Id.
Then you sort by whether str is DifficultyScoreStrategy and str.Question.Difficulty == question.Difficulty.
(Note that since false comes before true, you'll have to invert the conditions)
Then you can just do FirstOrDefault() ?? defaultStrategy.
Example:
var defaultStrategy = new() { Correct = 1, Incorrect = 0, Unattempted = 0 };
var selectedStrategy = Strategies.OrderBy(str =>
!(str is QuestionScoreStrategy questionStrat && questionStrat.Question.Id == question.Id)
).ThenBy(str =>
!(str is DifficultyScoreStrategy difficultySrat && difficultySrat.Difficulty == question.Difficulty)
).FirstOrDefault() ?? defaultStrategy;
You can easily add more "levels" to this by adding more ThenBy clauses.
I imagine that all data (questions, strategies, quizes is stored in database). Then I would expect such ways of getting each strategy:
Question strategy
var questionStrategy = dbContext.ScoreStrategies.SingleOrDefault(ss => ss.QuesionId == question.Id);
Difficulty strategy:
var difficultyStrategy = dbContext.ScoreStrategies.SingleOrDefault(ss => ss.Difficulty == question.Difficulty);
Default strategy for quiz:
var quizStrategy = dbContext.ScoreStrategies.SingleOrDefault(ss => ss.QuizId == question.QuizId)
Building on this and what you already provided, strategy is just three numbers: points for correct answer, points for incorrect and unattempted answer.
So this makes perfect candidate for abstract class, which would serve for base class for three entities - three types of strategy - those will be three tables, because each has different relations:
public abstract class ScoreStrategy
{
public double Correct { get; set; }
public double Incorrect { get; set; }
public double Unattempted { get; set; }
}
// Table with FK relation to Questions table
public class QuestionScoreStrategy : ScoreStrategy
{
public Question { get; set; }
public int QuestionId { get; set; }
}
// If you have table with difficulties, there should be FK relation to it.
// If you do not have table - it's worth consideration, you could then
// easily add more difficulties.
public class DifficultyStrategy : ScoreStrategy
{
public QuestionDifficulty Difficulty { get; set; }
}
// FK relation to Quizes table
public class QuizScoreStrategy : ScoreStrategy
{
public Quiz { get; set; }
public int QuizId { get; set; }
}
This way you end up with well grained tables that stores only relevant data.
Then, usage would become:
// Ideally, this method should be in some repoistory (look at repository design pattern) in data access layer
// and should leverage usage of async / await as well.
public ScoreStrategy GetScoreStrategy(Question question)
{
return dbContext.QuestionScoreStrategies.SingleOrDefault(qs => qs.QuestionId == question.Id)
?? dbContext.DifficultyStrategies.SingleOrDefault(ds => ds.Difficulty == question.Difficulty)
?? dbContext.QuizScoreStrategies.SingleOrDefault(qs => qs.QuizId == question.QuizId);
}
Then you could use this method in such way:
// This should be outside data access layer. Here you perform logic of getting question.
// This could be some ScoringManager class which should be singleton (one instance only).
// Then you could define fallback in private fields:
private readonly double FALLBACK_CORRECT_SCORE;
private readonly double FALLBACK_INCORRECT_SCORE;
private readonly double FALLBACK_UNATTEMPTED_SCORE;
// private constructor, as this should be singleton
private ScoringManager(double correctScore, double incorrectScore, double unattemptedScore)
=> (FALLBACK_CORRECT_SCORE, FALLBACK_INCORRECT_SCORE, FALLBACK_UNATTEMPTED_SCORE) =
(correctScore, incorrectScore, unattemptedScore);
public double CalcScoreForQuestion(Question question)
{
var scoreStrategy = GetScoreStrategy(question);
if (question answered correctly)
return scoreStrategy?.Correct ?? FALLBACK_CORRECT_SCORE;
if (question answered incorrectly)
return scoreStrategy?.Incorrect ?? FALLBACK_INCORRECT_SCORE;
if (question unattempted)
return scoreStrategy?.Unattempted ?? FALLBACK_UNATTEMPTED_SCORE;
}
NOTE
This is just the draft how I would organize things and most probably when writing code I would come up with improvements, but I think this is direction to go. For example ScoringManager could have ConfigureFallbackScore method, which would allow dynamically changing fallback scores (this would require making respective fields not readonly).
UPDATE
Define fallback strategy, in order to do that define enum:
public enum FallbackLevel
{
Difficulty,
Question,
Quiz,
}
Then scoring manager could have method to configure strategy (together with backing fields):
private FallbackLevel _highPrecedence;
private FallbackLevel _mediumPrecedence;
private FallbackLevel _lowPrecedence;
public void ConfigureFallbackStrategy(FallbackLevel highPrecedence, FallbackLevel mediumPrecedence, FallbackLevel lowPrecedence)
{
_highPrecedence = highPrecedence;
_mediumPrecedence = mediumPrecedence;
_lowPrecedence = lowPrecedence;
}
Then we would write getting strategy logic in manager:
public ScoreStrategy GetScoreStrategy(Question question)
{
var scoreStrategy = GetScoreStrategy(_highPrecedence, question)
?? GetScoreStrategy(_mediumPrecedence, question)
?? GetScoreStrategy(_lowPrecedence, question);
}
private ScoreStrategy GetScoreStrategy(FallbackLevel lvl, Question question) => lvl switch
{
FallbackLevel.Difficulty => dbContext.DifficultyStrategies.SingleOrDefault(ds => ds.Difficulty == question.Difficulty),
FallbackLevel.Question => dbContext.QuestionScoreStrategies.SingleOrDefault(qs => qs.QuestionId == question.Id),
FallbackLevel.Quiz => dbContext.QuizScoreStrategies.SingleOrDefault(qs => qs.QuizId == question.QuizId),
}
This way it is super easy to configure fallback strategy any way you want. Of course, there are some considerations still:
make sure that all fallback strategies are unique, so for example it is impossible to have high, medium and low startegy the same,
db context should be accessed only via repository pattern
add some more sanity checks (like nulls etc.)
I omitted those parts, as I focused on sheer functionality.

Prototype pattern: ensure clone is in a valid state

LogEvent represents information like log level, message, user, process name, ...
Some of these properties' values require pretty much effort for generation, e. g. the process name. Those properties' generated values are usually not changed, BUT despite this fact it should be possible to change them.
I considered the prototype pattern starting with a protoype, whose generic properties are pre-allocated. The protoype stays the same object during the lifetime of the application, but its properties' values might change as described above. New LogEvent objects should use the current prototype's values, objects created before the change should continue using the old values, that means, referencing the prototype from the "real" LogEvent object is not an option.
However the "real" LogEvent requires some properties to be not null, whereas this requirement is not useful for the prototype. I would like to prevent invalid objects of LogEvent. However if I use usual protoype pattern I would have to add a constructor to create the prototype, but this constructor would not create a valid object and I want to avoid, that an invalid object (the prototype itself or a clone of it) is used accidentally.
I spent some time on searching a solution, but the approaches listed below are pretty ugly. I hope, that there is an elegant solution. Meanwhile I tend to option 3, because 1 and 2 do not seem to be clean.
General structure
public interface ILogEvent
{
string PreAllocatedProperty1 { get; set; }
string PreAllocatedProperty2 { get; set; }
string IndividualProperty1 { get; set; }
string IndividualProperty2 { get; set; }
}
Option 1
Pros
LogEventPrototype can not be used as ILogEvent.
properties do not have to be declared in multiple classes
Cons
properties have to be mapped manually
static methods => interface for prototypes not possible
Code
class LogEventPrototype
{
public string PreAllocatedProperty1 { get; set; }
public string PreAllocatedProperty2 { get; set; }
public string IndividualProperty1 { get; set; }
public string IndividualProperty2 { get; set; }
public LogEventPrototype() { GeneratePreAllocatedProperties(); }
private void GeneratePreAllocatedProperties()
{
// if you invoke the helper functions later again,
// they might return different results (e. g.: user identity, ...)
PreAllocatedProperty1 = Helper.ComplexFunction();
PreAllocatedProperty2 = Helper.AnotherComplexFunction();
}
}
class LogEvent : LogEventPrototype, ILogEvent
{
// just for creating the prototype, object will be in an INVALID state
private LogEvent() : base() { }
// object will be in a VALID state
public LogEvent(string individualProperty2)
: this()
{
if (individualProperty2 == null)
throw new ArgumentNullException();
IndividualProperty2 = individualProperty2;
}
public static LogEvent FromPrototype(LogEventPrototype prototype)
{
// clone manually
return new LogEvent(prototype.IndividualProperty2)
{
IndividualProperty1 = prototype.IndividualProperty1,
PreAllocatedProperty1 = prototype.PreAllocatedProperty1,
PreAllocatedProperty2 = prototype.PreAllocatedProperty2
};
}
}
Option 2
Similar to option 1, but:
Pros
it is "ensured", that LogEventPrototype is never instantiated, it is just used as return type
no manual mapping
Cons: It seems to be hacky.
class LogEventPrototype
{
// properties ... (same as in option 1)
protected LogEventPrototype()
{
GeneratePreAllocatedProperties();
}
}
class LogEvent : LogEventPrototype, ILogEvent
{
// constructors same as in option 1; FromPrototype() removed
public static LogEventPrototype CreateProtoype()
{
return new LogEvent();
}
public static LogEvent FromPrototype(LogEventPrototype prototype)
{
if(prototype.IndividualProperty2 == null)
throw new ArgumentException();
return (LogEvent)prototype;
}
public static LogEventPrototype CreateProtoype()
{
return new LogEvent();
}
}
Option 3
Do not use a dedicated class for prototypes, but make the LogEvent constructor public and risk invalid LogEvent objects. Use a Validate() method instead and hope, that a client does not forget to use it.

Method to handle objects with properties in common, but different object types

I have a large collection of automatically generated objects. Although they are all of different, non-related classes, all of the objects share some basic properties (name, id, etc.). I do not control the generation of these objects, so unfortunately I cannot take the ideal approach of implementing an interface. I would like to create a method in which I pass an arbitrary one of these objects and do something using these common properties.
The general idea would be something like:
someObj a = new someObj();
a.name = "sara";
diffObj b = new diffObj();
b.name = "joe";
string phrase = string.Format("I am with {0} and {1}",
getName(a), getName(b));
private string getName(object anyObjWithName)
{
return anyObjWithName.name;
}
though naturally this does not work.
I thought a generic method might hold the answer, but the only way I can see to call it with the current type is using genericMethod.Invoke , which still carries the same issue of not being able to resolve the properties of the passed object in the method. This is unlike Calling generic method with a type argument known only at execution time or How to call generic method with a given Type object? where only the type, or properties of the type, are used in the method, as opposed to properties of the object.
I am aware that this would be (very) prone to error, but I can guarantee that all objects encountered will have the common properties being manipulated.
I can guarantee that all objects encountered will have the common properties being manipulated
If that's the case, you can use dynamic:
private string getName(dynamic anyObjWithName)
{
return anyObjWithName.name;
}
Be aware that using any object that does not have a name property will not fail until run-time.
If you want to add a little bit of safety you can catch the RuntimeBinderException that gets thrown if the property does not exist:
private string getName(dynamic anyObjWithName)
{
try {
return anyObjWithName.name;
}
catch(RuntimeBinderException) {
return "{unknown}";
}
}
If you're unhappy with the performance using dynamic as mentioned by D Stanley, you could always try FastMember.
All you need to know to start using it is pretty much shown in the first 2 code examples.
You are creating a Rube Goldberg device there. You should just have all your data objects classes implement a single interface, then you can work on that. Much simpler and less error prone than fiddling with reflection.
The very fact that a lot of objects have common properties but don't share the same ancestry, on in the very least a common interface, shows that something is wrong with your design. Do rethink it.
Multiple ways to accomplish this, simplest probably is to create Interface and declare common methods there, have your object implement it, then change "getName" method take interface object
private string getName(IMyInterface anyObjWithName)
{
return anyObjWithName.name;
}
The correct way to do this is with an interface, if you own the types that you're working with
public interface IEntity
{
int ID { get; set; }
string Name { get; set; }
}
public class TypeOne : IEntity
{
public int ID { get; set; }
public string Name { get; set }
public string BespokePropertyOne { get; set;}
}
public class TypeTwo : IEntity
{
public int ID { get; set; }
public string Name { get; set; }
public float BespokePropertyTwo { get; set; }
}
static void Main(string[] args)
{
List<IEntity> entities = new List<IEntity>();
entities.Add(new TypeOne() { ID = 1, Name = "Bob", BespokePropertyOne = "blablabla" });
entities.Add(new TypeTwo() { ID = 2, Name = "Alice", BespokePropertyTwo = 5.4f });
foreach (IEntity entity in entities)
{
Console.WriteLine("ID: {0} Name: {1}", entity.ID, entity.Name);
}
}
This answer was written before the edit to the question stating that interfaces weren't possible in this case. Perhaps it can help someone else reading this question.
Interface:
interface Iname
{
string Name { get; set; }
}
Use interface:
class A : Iname
{
public string Name { get; set; }
}
class B : Iname
{
public string Name { get; set; }
}
The method:
string GetName(Iname o)
{
return o.Name;
}
Use:
A a = new A { Name = "First" };
B b = new B { Name = "Last" };
Text = GetName(a) + " " + GetName(b);

Architecturally speaking, how should I replace an extremely large switch statement with something more manageable?

EDIT 1: Forgot to add the nested property curve ball.
UPDATE: I have chosen #mtazva's answer as that was the preferred solution for my specific case. In retrospect, I asked a general question with a very specific example and I believe that ended up confusing everyone (or maybe just me) as to what the question was exactly. I do believe the general question has been answered as well (see the Strategy pattern answers and links). Thanks everyone!
Large switch statements obviously smell and I have seen some links on how you could do this with a dictionary that maps to functions. But I'm wondering if there is a better (or smarter way) to do this? In a way, this is a question I've always sort of had rolling around in the back of my head but never really had a good solution to.
This question stemmed from another question I asked earlier: How to select all the values of an object's property on a list of typed objects in .Net with C#
Here is an example class I'm working with (from an external source):
public class NestedGameInfoObject
{
public string NestedName { get; set; }
public int NestedIntValue { get; set; }
public decimal NestedDecimalValue { get; set; }
}
public class GameInfo
{
public int UserId { get; set; }
public int MatchesWon { get; set; }
public long BulletsFired { get; set; }
public string LastLevelVisited { get; set; }
public NestedGameInfoObject SuperCoolNestedGameInfo { get; set; }
// thousands more of these
}
Unfortunately, this is coming from an external source... imagine a HUGE data dump from Grand Theft Auto or something.
And I want to get just a small cross section of a list of these objects. Imagine we want to be able to compare you with a bunch of your friends' game info objects. An individual result for one user would look like this:
public class MyResult
{
public int UserId { get; set; } // user id from above object
public string ResultValue { get; set; } // one of the value fields from above with .ToString() executed on it
}
And an example of what I want to replace with something more manageable (believe me, I DON'T want to be maintaining this monster switch statement):
const int MATCHES_WON = 1;
const int BULLETS_FIRED = 2;
const int NESTED_INT = 3;
public static List<MyResult> GetMyResult(GameInfo[] gameInfos, int input)
{
var output = new List<MyResult>();
switch(input)
{
case MATCHES_WON:
output = gameInfos.Select(x => new MyResult()
{
UserId = x.UserId,
ResultValue = x.MatchesWon.ToString()
}).ToList<MyResult>();
break;
case BULLETS_FIRED:
output = gameInfos.Select(x => new MyResult()
{
UserId = x.UserId,
ResultValue = x.BulletsFired.ToString()
}).ToList<MyResult>();
break;
case NESTED_INT:
output = gameInfos.Select(x => new MyResult()
{
UserId = x.UserId,
ResultValue = x.SuperCoolNestedGameInfo.NestedIntValue.ToString()
}).ToList<MyResult>();
break;
// ad nauseum
}
return output;
}
So the question is are there any reasonable ways to manage this beast? What I'd really like is a dynamic way to get this info in case that initial object changes (more game info properties are added, for instance). Is there a better way to architect this so it's less clumsy?
I think your first sentence eluded to what is probably the most reasonable solution: some form of dictionary mapping values to methods.
For example, you could define a static Dictionary<int, func<GameInfo, string>>, where each value such as MATCHES_WON would be added with a corresponding lambda that extracts the appropriate value (assuming your constants, etc are defined as shown in your example):
private static Dictionary<int, Func<GameInfo, string>> valueExtractors =
new Dictionary<int, Func<GameInfo, string>>() {
{MATCHES_WON, gi => gi.MatchesWon.ToString()},
{BULLETS_FIRED, gi => gi.BulletsFired.ToString()},
//.... etc for all value extractions
};
You can then use this dictionary to extract the value in your sample method:
public static List<MyResult> GetMyResult(GameInfo[] gameInfos, int input)
{
return gameInfo.Select(gi => new MyResult()
{
UserId = gi.UserId,
ResultValue = valueExtractors[input](gi)
}).ToList<MyResult>();
}
Outside of this option, you could potentially have some sort of file/database/stored lookup with the number and the property name, then use reflection to extract the value, but that would obviously not perform as well.
I think this code is getting out of hand a bit. You're effectively using constants to index properties - and this is creating fragile code that you're looking to use some technique - such as - reflection, dictionaries, etc - to control the increased complexity.
Effectively the approach that you're using now will end up with code like this:
var results = GetMyResult(gameInfos, BULLETS_FIRED);
The alternative is to define an extension method that lets you do this:
var results = gameInfos.ToMyResults(gi => gi.BulletsFired);
This is strongly-typed, it doesn't require constants, switch statements, reflection, or anything arcane.
Just write these extension methods and you're done:
public static class GameInfoEx
{
public static IEnumerable<MyResult> ToMyResults(
this IEnumerable<GameInfo> gameInfos,
Func<GameInfo, object> selector)
{
return gameInfos.Select(gi => gi.ToMyResult(selector));
}
public static MyResult ToMyResult(
this GameInfo gameInfo,
Func<GameInfo, object> selector)
{
return new MyResult()
{
UserId = gameInfo.UserId,
ResultValue = selector(gameInfo).ToString()
};
}
}
Does that work for you?
You can use reflection for theses purposes. You can implement custom attributes, mark your properties, etc. Also, it is dynamic way to get info about your class if it changes.
If you want to manage switch code I would point you at Design Patterns book (GoF) and suggest possibly looking at patterns like Strategy and possibly Factory (thats when we talk about general case use, your case isn't very suited for Factory) and implementing them.
While switch statement still has to be left somewhere after refactoring to pattern is complete (for example, in a place where you select strategy by id), code will be much more maintanable and clear.
That said about general switch maintenance, if they become beast like, I am not sure its best solution given how similar your case statements look.
I am 100% sure you can create some method (possibly an extension method) that will be accepting desired property accessor lambda, that should be used when results are generated.
If you want your code to be more generic, I agree with the suggestion of a dictionary or some kind of lookup pattern.
You could store functions in the dictionary, but they seemly all perform the same operation - getting the value from a property. This is ripe for reflection.
I'd store all your properties in a dictionary with an enum (prefer an enum to a const) as the key, and a PropertyInfo - or, less preferred, a string which describes the name of the property - as the value. You then call the GetValue() method on the PropertyInfo object to retrieve the value from the object / class.
Here's an example where I'm mapping enum values to their 'same named' properties in a class, and then using reflection to retrieve the values out of a class.
public enum Properties
{
A,
B
}
public class Test
{
public string A { get; set; }
public int B { get; set; }
}
static void Main()
{
var test = new Test() { A = "A value", B = 100 };
var lookup = new Dictionary<Properties, System.Reflection.PropertyInfo>();
var properties = typeof(Test).GetProperties().ToList();
foreach (var property in properties)
{
Properties propertyKey;
if (Enum.TryParse(property.Name, out propertyKey))
{
lookup.Add(propertyKey, property);
}
}
Console.WriteLine("A is " + lookup[Properties.A].GetValue(test, null));
Console.WriteLine("B is " + lookup[Properties.B].GetValue(test, null));
}
You can map your const values to the names of the properties, PropertyInfo objects which relate to those properties, functions which will retrieve the property values... whatever you think suits your needs.
Of course you will need some mapping - somewhere along the way you will be depending on your input value (the const) mapping to a specific property. The method by which you can get this data might determine the best mapping structure and pattern for you.
I think the way to go is indeed some kind of mapping from one value (int) to something that is somehow a function that knows how to extract a value.
If you really want to keep it extensible, so that you can easily add some without touching the code, and possibly accessing more complex properties (ie. nested properties, do some basic computation), you may want to keep that in a separate source.
I think one way to do this is to rely on the Scripting Services, for instance evaluating a simple IronPython expression to extract a value...
For instance in a file you could store something like :
<GameStats>
<GameStat name="MatchesWon" id="1">
<Expression>
currentGameInfo.BulletsFired.ToString()
</Expression>
</GameStat>
<GameStat name="FancyStat" id="2">
<Expression>
currentGameInfo.SuperCoolNestedGameInfo.NestedIntValue.ToString()
</Expression>
</GameStat>
</GameStats>
and then, depending on the requested stat, you always end up retrieving the general GameInfos. You can them have some kind of foreach loop with :
foreach( var gameInfo in gameInfos){
var currentGameInfo = gameInfo
//evaluate the expression for this currentGameInfo
return yield resultOfEvaluation
}
See http://www.voidspace.org.uk/ironpython/dlr_hosting.shtml for examples on how to embed IronPython Scripting in a .NET application.
NOTE: when working with this kind of stuff, there are several things you must really be careful about:
this potentially allows someone to inject code in your application ...
you should measure the performance impact of Dynamic evaluation in here
I don't have a solution to your switch problem off the top of my head, but you could certainly reduce the code by using a class that can automatically map all the fields you need. Check out http://automapper.org/.
I would not have written the GetMyResult method in the first place. All it is doing is transforming GameInfo sequence into MyResult sequence. Doing it with Linq would be easier and more expressive.
Instead of calling
var myResultSequence = GetMyResult(gameInfo, MatchesWon);
I would simply call
var myResultSequence = gameInfo.Select(x => new MyResult() {
UserId = x.UserId,
ResultValue = x.MatchesWon.ToString()
});
To make it more succinct you can pass the UserId and ResultValue in constructor
var myResultSequence =
gameInfo.Select(x => new MyResult(x.UserId, x.MatchesWon.ToString()));
Refactor only if you see the selects getting duplicated too much.
This is one possible way without using reflection:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
public class GameInfo
{
public int UserId { get; set; }
public int MatchesWon { get; set; }
public long BulletsFired { get; set; }
public string LastLevelVisited { get; set; }
// thousands more of these
}
public class MyResult
{
public int UserId { get; set; } // user id from above object
public string ResultValue { get; set; } // one of the value fields from above with .ToString() executed on it
}
public enum DataType
{
MatchesWon = 1,
BulletsFired = 2,
// add more as needed
}
class Program
{
private static Dictionary<DataType, Func<GameInfo, object>> getDataFuncs
= new Dictionary<DataType, Func<GameInfo, object>>
{
{ DataType.MatchesWon, info => info.MatchesWon },
{ DataType.BulletsFired, info => info.BulletsFired },
// add more as needed
};
public static IEnumerable<MyResult> GetMyResult(GameInfo[] gameInfos, DataType input)
{
var getDataFunc = getDataFuncs[input];
return gameInfos.Select(info => new MyResult()
{
UserId = info.UserId,
ResultValue = getDataFunc(info).ToString()
});
}
static void Main(string[] args)
{
var testData = new GameInfo[] {
new GameInfo { UserId="a", BulletsFired = 99, MatchesWon = 2 },
new GameInfo { UserId="b", BulletsFired = 0, MatchesWon = 0 },
};
// you can now easily select whatever data you need, in a type-safe manner
var dataToGet = DataType.MatchesWon;
var results = GetMyResult(testData, dataToGet);
}
}
}
Purely on the question of large switch statements, it is notable that there are 2 variants of the Cyclomatic Complexity metric in common use. The "original" counts each case statement as a branch and so it increments the complexity metric by 1 - which results in a very high value caused by many switches. The "variant" counts the switch statement as a single branch - this is effectively considering it as a sequence of non-branching statements, which is more in keeping with the "understandability" goal of controlling complexity.

Categories