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.

Greg
  • 430
  • 1
  • 5
  • 17
  • 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? – MakePeaceGreatAgain Jun 06 '18 at 14:17
  • 1
    It is quite interesting... is the the `new { ... }` implicit in point 1? – xanatos Jun 06 '18 at 14:17
  • 2
    I'm not sure what question #1 even *means*. Translating comma-separated arguments into a single argument? –  Jun 06 '18 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 Jun 06 '18 at 14:19
  • Point 1 would be down the the language (C#) specs and the compiler, not .Net as that is just a framework – musefan Jun 06 '18 at 14:19
  • Ah no... The class is dynamic and is handling in a dynamic way the use of the method... fascinating – xanatos Jun 06 '18 at 14:19
  • 1
    Taking a look at the library, it looks like the `Evaluate` method is overloaded as well, so the example you posted is most likely invoking the method with the signature `private decimal Evaluate(string expression, Dictionary arguments)`. – awh112 Jun 06 '18 at 14:19
  • No no... The first step is [`public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)`](https://github.com/Giorgi/Math-Expression-Evaluator/blob/abc2287f419212b81b39ded8d863debbfed89a58/SimpleExpressionEvaluator/ExpressionEvaluator.cs#L81)... Wow! – xanatos Jun 06 '18 at 14:20
  • 2
    The link you give is a link to the source code, why not read it and find out how it works? – Neil Jun 06 '18 at 14:20
  • 1
    @Neil Because I too don't know exactly how it works and I'm quite good with C#? This is probably the first example I've ever seen of subclassing `DynamicObject` just to do that trick. – xanatos Jun 06 '18 at 14:22
  • 2
    Can't decide if this library is evil, genius or evil genius... – DavidG Jun 06 '18 at 14:28
  • @xanatos It's open now, you can post. Maybe worth mentioning that `engine` also needs to be declared as `dynamic` – DavidG Jun 06 '18 at 14:34
  • This question shouldn't be closed, specially with the edit. The OP is clearly asking how the compiler is able to resolve overloads that are seemingly not declared in the linked code... Xanatos has answered how in comments. – InBetween Jun 06 '18 at 14:34
  • @Neil I did read the code.. and I basically understand what it's doing. I just don't know how to create an object that would match the signature. – Greg Jun 06 '18 at 14:35

1 Answers1

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

xanatos
  • 109,618
  • 12
  • 197
  • 280
  • 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 Jun 06 '18 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 Jun 06 '18 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 Jun 06 '18 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 Jun 06 '18 at 14:45