开发者

Evaluating a set of rules defined in a string

I made a system that creates a simple string with Function/Response format, example:

Check('Value'):ShowImage(@)|Check('Value'):OtherFunction(@)....and so on

Where Check is the name of a function, Value is the parameter, ShowImage is the name of a Response function, @ is the entry paremeter (result of the previous function). The pipe splits another Function/Response pair that fires if the first Check('Value') function once "checked" were not satisfied (say, if the parameter was not accomplished the Check condition the fu开发者_如何转开发nction is invalid and hence the Response part in the first Function/Response pair is not executed, so system keep trying Functions awaiting to find the one that executes the right Response).

The way the application should work is to evaluate each rule (similar to a JavaScript eval function) and take appropriate action based on function results.

At first glance, it looks complicated, because first of all I need to cast the string to the right real C# function that will actually process the condition. Therefore, depending on the function result, decide where to point to execute my Response function.

Furthermore: This is just the kind example, because there are functions as * that represent something like: "any condition is true" what in almost all cases this function is the last in the chain (the default function).

That's my problem, I can't realize what is the easiest way to cope with this problem. Maybe a chain of delegates? Lambdas? Anonymous stored into a structure...

Could you give me your measure/advise? Where to start?


Depending on the level of extensibility you want to have, I would say the most extensible way would be to use reflection to get method references, after you have parsed the input string.

You can start by splitting your problem into smaller subproblems.

Let's say you are aiming for something like this:

static void Main(string[] args)
{
    string rules = 
        "Check(Morning):Say(Good morning)|" +
        "Check(Afternoon):Say(Good afternoon)|" +
        "Check(Evening):Say(Good night)";

    // next, you need some **object instances** which will 
    // provide a context for your "test" and "action" methods.
    // you don't want to use static methods if you
    // went through the pain of defining such an architecture!

    // let's say that a "Tester" object has a "Check" method,
    // and an "Executor" object has a "Say" method:

    var tester = new Tester("Afternoon");
    var executor = new Executor();

    // since I suck at regular expressions,
    // I am using plain string methods to split
    // the expression into tokens. You might want
    // to add some validation

    foreach (var rule in rules.Split('|'))
    {
        var result = Parse(rule, tester, executor);
        if (result.TestPassed)
        {
            result.Execute();
            break;
        }
    }
}

A "result" as it's used above would then have an interface like this:

public interface IResult
{
    // returns true if a test is fulfilled
    bool TestPassed { get; }

    // executes the related action
    void Execute();
}

And, if you want to delegate actual actions to some unknown methods, a reasonable way to implement it would be something like this:

public class Result : IResult
{
    #region IResult Members

    private readonly Func<bool> _testMethod;
    public bool TestPassed
    {
        get { return _testMethod(); }
    }

    private readonly Action _actionMethod;
    public void Execute()
    {
        _actionMethod();
    }

    #endregion

    public Result(Func<bool> testMethod, Action actionMethod)
    {
        _testMethod = testMethod;
        _actionMethod = actionMethod;
    }
}

What's left is to use some reflection to get the actual methods out of your strings:

private static IResult Parse(string rule, object tester, object executor)
{
    // split into test/action
    var tokens = rule.Split(':');

    // extract the method/parameter part for each expression
    var test = GetMethodAndParams(tokens[0]);
    var action = GetMethodAndParams(tokens[1]);

    // use reflection to find actual methods
    var testMethod = tester.GetType().GetMethod(test.Method);
    var actionMethod = executor.GetType().GetMethod(action.Method);

    // return delegates which will simply invoke these methods
    return new Result
    (
        () => (bool)testMethod.Invoke(tester, new object[] { test.Param }),
        () => actionMethod.Invoke(executor, new object[] { action.Param })
    );
}

That is, more or less, your program's skeleton. You should be able to fill in the missing parts yourself, as an exercise. If you have problems, I can update the answer later.

A GetMethodAndParams method should split the input string into a Tuple (or your custom class) which contains the method name and its params as plain strings. Tester and Executor classes can also be implemented trivially.


It looks like you want a pattern along the lines of the .NET TryParse() methods. In that case, you would modify your check method to have an out parameter that is the value (represented in your example by @).

int result;

if(Check('Value', out result))
    ShowImage(result);
else(Check('Value2', out result))
    OtherFunction(result);


Finally I'm back here to post what I've done few weeks ago to solve this situation. It was easy.

Regex class provide few options, one of those is "Explicit Catpure", all streams with the form (?) can be handled as strong typed parameters so, if the named group "IsNotEmptyorNull" then the function is present and it is promotable to casting using the form Enum.Parse("").

Snipet:

Regex rx = new Regex(@"(?<function>Check|BooleanOp)\('(?<param>[\w]+)'\){1}:(?<action>[\w]+){1}", RegexOptions.ExplicitCapture);

Match m;
Dictionary<FunctionArray, String> actions = new Dictionary<FunctionArray, String>();

if((m=rx.Match(strStream)).Success)
{
   actions.Add((FunctionArray)Enum.Parse(typeof(FunctionArray), m.Groups["function"].value, true), m.Groups["param"].value);
}

Of course, there are lost the action part so I've improved the Dictionary stuff with an specialized Struct that can handle the functions and values as a source for decision taking.

Thanks to all. Ed.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜