6

First, I don't have much experience in .Net - especially within the last 7 years.

I'm trying to develop an application and would to incorporate another library (https://github.com/Giorgi/Math-Expression-Evaluator)

That library allows me to evaluate math expressions like Evaluate("a+b", a: 1,b: 1). The method signature is public decimal Evaluate(string expression, object argument = null)

  1. I would like to understand better how .Net translates comma-separated arguments into a single "argument".
  2. I'm not sure how to create that argument dynamically.. for example, iterating through a list of values and creating an object that will match the appropriate argument for that signature.

I'm really just looking for pointers for documentation and more information.. Thanks for anything.

EDIT: Sorry.. purposely left it broad because I wasn't looking for people to do my work for me.. just can't seem to find a starting point to do my own research.

The method is called like

dynamic engine = new ExpressionEvaluator() ; 
engine.Evaluate("(c+b)*a", a: 6, b: 4.5, c: 2.6)) ; 

In the body of Evalute() is this code (which turns that argument into a Dictionary of String, Decimal pairs.

if (argument == null)
        {
            return new Dictionary<string, decimal>();
        }

        var argumentType = argument.GetType();

        var properties = argumentType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
            .Where(p => p.CanRead && IsNumeric(p.PropertyType));

        var arguments = properties.ToDictionary(property => property.Name,
            property => Convert.ToDecimal(property.GetValue(argument, null)));

        return arguments;

What I'd like to be able to do is parse a String like "a:1,b:2" and turn it into an object that matches that Evaluate() signature.

14
  • 2
    Even question 1 is by far too broad and contains many steps. Not to mentioned question 2. "Understand better" imples that you already understand "something". So what do you understand and what don´t you? Commented Jun 6, 2018 at 14:17
  • 2
    I'm not sure what question #1 even means. Translating comma-separated arguments into a single argument?
    – user47589
    Commented Jun 6, 2018 at 14:18
  • 2
    Unless I'm misunderstanding something in C# myself, I don't see how the example use of Evaluate() matches that method signature at all.
    – David
    Commented Jun 6, 2018 at 14:19
  • 2
    The link you give is a link to the source code, why not read it and find out how it works?
    – Neil
    Commented Jun 6, 2018 at 14:20
  • 2
    Can't decide if this library is evil, genius or evil genius...
    – DavidG
    Commented Jun 6, 2018 at 14:28

1 Answer 1

11

That library is using high level magic... Very high level :-)

The trick is that the class is declared as:

public class ExpressionEvaluator : DynamicObject

So it is a class that implements the dynamic magic introduced in .NET 4.0

Now... In the class there are two Evaluate methods:

public decimal Evaluate(string expression, object argument = null)

and

private decimal Evaluate(string expression, Dictionary<string, decimal> arguments)

The only method normally visible and usable is the first one. It is used like:

engine.Evaluate("a + b + c", new { a = 1, b = 2, c = 3 });

The new { ... } creates an anonymous object, that is then "unpacked" here through the use of reflection to a Dictionary<string, decimal> to be fed to the private Evaluate().

If you try to use the other notation, the one like:

engine.Evaluate("a + b + c", a: 1, b: 2, c: 3 });

then the .NET can't match the method to the public Evaluate() that is present, but the class, being a subclass of DynamicObject, causes the C# compiler to write some "magic" code that launches this method (that is still implemented by the ExpressionEvaluator):

public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)

That first checks that we want to call Evaluate:

if (nameof(Evaluate) != binder.Name)

and if we are trying to call Evaluate, it unpacks the parameters to a new Dictionary<string, decimal>() and then calls the private Evaluate().

As a sidenote, to use the "dynamic" way of writing Evaluate you have to declare the engine variable like;

dynamic dynamicEngine = new ExpressionEvaluator();

So using the dynamic variable type.

Now... As the library is written you can:

  • Use an anonymous object, with the problem that anonymous objects must have their "shape" defined at compile time (so at compile time you must know that you will need a a, a b and a c. You can't need a d at runtime if you didn't create a new { a, b, c, d } at compile time). See for example a response I gave three years ago about how to create dynamic anonymous types at runtime. One of the reasons I gave for that block of code was:

    there are parts of the .NET framework that heavily use reflection to render objects (for example all the various datagrids). These parts are incompatible with dynamic objects and often don't support object[]. A solution is often to encapsulate the data in a DataTable... or you can use this :-)

    Note that in one of the comments to that response there is a link to a modified version of my code used by one of the many implementations of Dynamic.Linq.

  • Use a non-anonymous object (a new Foo { a = 1, b = 2 c = 3 }). The library doesn't make distinctions between anonymous and non-anonymous objects. So same limitation as before, because at compile time you need a Foo class with the right number of parameters

  • Use the dynamic notation. Sadly even that is quite static. You can't easily add new parameters, that for the number and name of the "variables" must be defined at compile time.

A possible solution is to modify the source code (it is a single file) and make public this method:

private decimal Evaluate(string expression, Dictionary<string, decimal> arguments)

then you can easily and dynamically populate the Dictionary<string, decimal> arguments

4
  • So I believe (to use the same signature), I would have to dynamically create one of these new { a = 1, b = 2, c = 3 } Not sure how to do that, but it's a starting point. Thanks
    – Greg
    Commented Jun 6, 2018 at 14:38
  • @Greg Yes. But note that the number and name of the variables would be fixed. Sadly the libary doesn't exposes the private Evaluate that uses the Dictionary<> method. That would be very easy to truly dynamically use.
    – xanatos
    Commented Jun 6, 2018 at 14:40
  • I agree, and I have cloned his library and will submit a pull request. But I wanted to make sure that there wasn't a way of using the signature as is before I started asking for additional features. Thanks again.
    – Greg
    Commented Jun 6, 2018 at 14:42
  • @Greg I feel the simplest way is to change from private to public and use that method. Because creating anonymous objects at runtime is quite complex.
    – xanatos
    Commented Jun 6, 2018 at 14:45

Not the answer you're looking for? Browse other questions tagged or ask your own question.