2

I have a list of products against which I need to create expression trees that can be persisted and later retrieved and executed. This is for a client-side calculation builder.

I am new to Expressions and although I have read a reasonable amount of documentation, the learning curve is a little steep here. What I want is to be able to accumulate PropertyExpression and Operand pairs to start with. Here is what I have so far and am not sure if I have structured it correctly.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace ConsoleApplication1
{
    public enum ProductType { Perishable, Fixed, Miscellaneous }    
    public enum OperandType { Addition, Sunbtraction, Multiplication, Division }

    public class Product
    {
        public string Name { get; set; }
        public ProductType Type { get; set; }
        public float Price { get; set; }
    }

    public class Configuration
    {
        public Dictionary<string, float> Dictionary { get; set; }
    }

    public class Operand
    {
        public OperandType Type { get; set; }
    }

    public class CalculationPair<TEntityType, TProperty>
        where TEntityType: class
        where TProperty: struct
    {
        public ValueTypeProperty<TEntityType, TProperty> Left { get; set; }
        public Operand Operand { get; set; }
        public ValueTypeProperty<TEntityType, TProperty> Right { get; set; }

        // How to specify TResult as an [out] parameter?
        public TResult Calculate<TResult> ()
            where TResult: struct
        {
            TResult result = default(TResult);

            if (this.Operand.Type == OperandType.Multiplication)
            {
                // How to execute the expression?
                //result = this.Left * this.Right;
            }

            return (result);
        }
    }

    public class ValueTypeProperty<TEntityType, TProperty>
        where TEntityType: class
        where TProperty: struct
    {
        public string Name { get; set; }

        public Expression<Func<TEntityType, TProperty>> PropertyExpression { get; set; }
    }

    public class ProductPriceProperty:
        ValueTypeProperty<Product, float>
    {
    }

    public static class Program
    {
        public static void Main ()
        {
            Configuration config = new Configuration();
            List<Product> products = new List<Product>();

            config.Dictionary.Add("ExportFactor", 80);
            config.Dictionary.Add("ChannelMargin", 100);

            products.Add(new Product() { Name = "1", Type = ProductType.Fixed, Price = 10 });
            products.Add(new Product() { Name = "2", Type = ProductType.Miscellaneous, Price = 20 });
            products.Add(new Product() { Name = "3", Type = ProductType.Perishable, Price = 30 });

            foreach (var product in products)
            {
                if (product.Type == ProductType.Fixed)
                {
                    CalculationPair<Product, float> calculation = new CalculationPair<Product, float>()
                    {
                        Left = new ProductPriceProperty() { Name = "Product Price", PropertyExpression = (entity => entity.Price) },
                        Operand = new Operand() { Type = OperandType.Multiplication },
                        Right = new ProductPriceProperty() { Name = "ExportFactor", PropertyExpression = (entity => config.Dictionary ["ExportFactor"]) },
                    };

                    // Calculation needs to be persisted to be retrieved later.
                    // ???!

                    // Once calculation has been reconstruction from the persistence layer, it needs to be executed.
                    product.Price = calculation.Calculate<float>();
                }
            }
        }
    }
}

UPDATE: Here is what I am struggling with in order of priority:

  • How to execute the expression in the CalculationPair.Calculate<TReult>() function?
  • How to specify TResult as an [out] parameter in the CalculationPair.Calculate<TReult>() function?
  • How to persist the calculation Expression and retrieve it later?
Raheel Khan
  • 14,205
  • 13
  • 80
  • 168
  • 2
    Why not use the built-in expression trees instead of rewriting everything from scratch? You will only need to provide serialization logic and everything else (including executing the expression later) comes absolutely free. – Jon Dec 29 '13 at 19:49
  • @Jon: Even though I have tried to read up on it, I am unclear about the difference between what I am attempting and what you are suggesting. This is to be used for a expression/calculation builder so having more control is preferable. If I not mistaken, most examples of expression builders on the net use the built-in trees and use them for filtering purposes as opposed to value manipulation based on formulas that the user specifies. – Raheel Khan Dec 29 '13 at 19:56
  • 1
    You are attempting to write from scratch a less well-designed and overwhelmingly less well-supported (by the compiler) subset of the built-in expression trees. I am suggesting using (an appropriate subset of) the built-ins -- as much as you need, your code won't handle all theoretical cases correctly and it does not need to. It's true that most examples are oriented towards filtering, but there is no hard restriction anywhere. You can use an expr tree to express anything that does not require mutating state, and also lots of things that do. – Jon Dec 29 '13 at 20:12
  • @Jon: Thank you for the explanation and I agree. Would you please point me to one of these examples as a starting point. Considering my experience based on this question. I guess I am having a hard time grasping how to start in the first place. – Raheel Khan Dec 29 '13 at 20:23

1 Answers1

1

As Jon said, you can use usual expression tress, or you can make closure anonymous method like in this code:

public class CalculationPair<TEntityType, TProperty>
    where TEntityType : class
    where TProperty : struct
{
    // not sure that first three properties are needed here
    public ValueTypeProperty<TEntityType, TProperty> Left { get; set; }
    public Operand Operand { get; set; }
    public ValueTypeProperty<TEntityType, TProperty> Right { get; set; }

    // closure method
    public Func<TEntityType, TProperty> Calculator { get; set; }
}

And here is part of Main method that uses it:

foreach (var product in products)
{
    if (product.Type == ProductType.Fixed)
    {
        CalculationPair<Product, float> calculation = new CalculationPair<Product, float>()
        {
            Left = new ProductPriceProperty() { Name = "Product Price", PropertyExpression = (entity => entity.Price) },
            Operand = new Operand() { Type = OperandType.Multiplication },
            Right = new ProductPriceProperty() { Name = "ExportFactor", PropertyExpression = (entity => config.Dictionary["ExportFactor"]) },

            // only this property is needed, and it will handle reference to config object in the closure
            Calculator = (entity) => entity.Price * config.Dictionary["ExportFactor"]
        };

        // Once calculation has been reconstruction from the persistence layer, it needs to be executed.
        product.Price = calculation.Calculator(product);
    }
}

In that sample there is no Expression trees, just usual closure method.

UPDATE1

The problem with your expressions for Left and Right nodes, is that each this expression is linked to own entity parameter instead of ParameterExpression that we create and that will point to real entity object, so we need to rewrite old one to new one it with ExpressionVisitor . It used for parsing and rewriting needs.

Here is code of that rewriter:

public class ParameterRewriter : ExpressionVisitor
{
    private readonly ParameterExpression _expToRewrite;

    public ParameterRewriter(ParameterExpression expToRewrite)
    {
        this._expToRewrite = expToRewrite;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        // we just use type checking to understand that it's our parameter, and we replace it with new one
        if (node.Type == this._expToRewrite.Type) return this._expToRewrite;
        return base.VisitParameter(node);
    }
}

And here is CalculationPair class:

public class CalculationPair<TEntityType, TProperty>
    where TEntityType : class
    where TProperty : struct
{
    public ValueTypeProperty<TEntityType, TProperty> Left { get; set; }
    public Operand Operand { get; set; }
    public ValueTypeProperty<TEntityType, TProperty> Right { get; set; }

    public TResult Calculate<TResult>(TEntityType entity)
        where TResult : struct
    {
        TResult result = default(TResult);

        var prop = Expression.Parameter(typeof(TEntityType), "param");
        var visitor = new ParameterRewriter(prop);
        var leftExp = visitor.Visit(Left.PropertyExpression.Body);
        var rightExp = visitor.Visit(Right.PropertyExpression.Body);

        Expression body;

        switch (this.Operand.Type)
        {
            case OperandType.Multiplication:
                body = Expression.Multiply(leftExp, rightExp);
                break;
            case OperandType.Addition:
                body = Expression.Add(leftExp, rightExp);
                break;
            case OperandType.Division:
                body = Expression.Divide(leftExp, rightExp);
                break;
            case OperandType.Sunbtraction:
                body = Expression.Subtract(leftExp, rightExp);
                break;
            default:
                throw new Exception("Unknown operand type");
        }

        var lambda = Expression.Lambda<Func<TEntityType, TResult>>(body, prop);

        // compilation is long operation, so you might need to store this Func as property and don't compile it each time
        var func = lambda.Compile();
        result = func(entity);

        return (result);
    }
}

And usage is the same

product.Price = calculation.Calculate<float>(product);
Sergey Litvinov
  • 7,408
  • 5
  • 46
  • 67
  • Thanks. Is there a way to have the `CalculationPair` class automatically populate the `Calculator` internally rather than setting it from the external initializer in `Main`? – Raheel Khan Dec 29 '13 at 20:19
  • In other words, I wanted to find out how to use the `ValueTypeProperty` Left and Right members from within the `CalculationPair` class. Your solution works of course but what I am asking might give me a starting point on understanding Expression> types better. – Raheel Khan Dec 29 '13 at 20:27
  • updated answer to use your nodes class for calculation purposes – Sergey Litvinov Dec 29 '13 at 20:47
  • Thank you very much. This is exactly the starting point I was looking for. – Raheel Khan Dec 29 '13 at 20:51
  • Just to confirm, when expanding on this code base, how can I tell if I am using built-in expression trees or defaulting to creation from scratch (as in my example code)? I don't yet have a holistic or birds-eye view of what differentiates closures, expressions and delegates. If this warrants a new questions, please let me know and I will invite you there. – Raheel Khan Dec 29 '13 at 20:59
  • 1
    This code is hybrid, as it uses your ValueTypeProperties and CalculationPair, and it also uses expressions for calculation logic. This code can't use just only Expression if you need both Left, Right nodes. Basically expression is just tree of some operations\objects, that can be parsed in runtime or it can be compiled to usual delegate. And closure is when one anonymous method uses variables from parameter, inner scope, so it locks to them. – Sergey Litvinov Dec 29 '13 at 21:20
  • 1
    Here is answer about expressions - http://stackoverflow.com/questions/10005948/what-are-expression-trees-and-how-do-you-use-them-and-why-would-you-use-them and about closures - http://stackoverflow.com/questions/595482/what-are-closures-in-c – Sergey Litvinov Dec 29 '13 at 21:21
  • 1
    Your comment `(This code is hybrid...)` really hits the spot. The understanding starts with compile-time delegates, moves on to `Func<>` that still requires compile-time declaration, and moves on to `Expression>` that can either be expressed at compile-time or created at runtime using the `expression trees API`. In order to develop a modest calculation builder, I would have to employ the last combining compile- and run- time declaration/creation respectively. Thank you again for a helping out way beyond expectation. – Raheel Khan Dec 29 '13 at 22:11