Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing property as parameter

Tags:

c#

I am creating a merit function calculator, which for the uninitiated takes a selection of properties, and calculates a value based on how close those properties are to some idealized values (the merit function). This then enables the user to find an item that most closely matches their requirements.

This is the code I'd like to use:

public class MeritFunctionLine
{
    public Func<CalculationOutput, double> property { get; set; }
    public double value { get; set; }
    public ComparisonTypes ComparisonType { get; set; }
}

public class MeritFunction
{
    public List<MeritFunctionLine> Lines { get; set; }
    public double Calculate(CalculationOutput values)
    {
        double m = 0;
        foreach (var item in Lines)
        {
            m += Math.Abs(values.property - item.value);
        }
        return m;
    }
}

public class CalculationOutput
{
    public double property1 { get; set; }
    public double property2 { get; set; }
    public double property3 { get; set; }
    public double property4 { get; set; }
}

Obviously this doesn't compile as values doesn't contain a member called property, but here is an explanation of what I want to do:

  1. Create a new MeritFunction
  2. Add an arbitrary number of MeritFunctionLines to MeritFunction.Lines
  3. The MeritFunctionLine.property should specify what property of CalculationOutput should be compared in MeritFunction.Calculate

i.e.

MeritFunction mf = new MeritFunction();
mf.Lines.Add(new MeritFunctionLine() { property = x => x.Property1, value = 90, comparisonType = ComparisonTypes.GreaterThan });
mf.Lines.Add(new MeritFunctionLine() { property = x => x.Property3, value = 50, comparisonType = ComparisonTypes.Equals });

CalculationOutput c1 = new CalculationOutput() { property1 = 1, property2 = 20, property3 = 150, property4 = 500 };
CalculationOutput c2 = new CalculationOutput() { property1 = 15, property2 = 32, property3 = 15, property4 = 45 };

double value1 = mf.Calculate(c1);
double value2 = mf.Calculate(c2);

I am not asking how to pass a property as a parameter into a function, which is prohibited by C#.

like image 707
Nick Avatar asked Aug 12 '14 12:08

Nick


People also ask

How to pass property name as parameter in C#?

You can pass a property accessor to the method. You would call it like this: var result = SortBy(toSort, x => x. maxSpeed);

Can I pass this as parameter?

"Can I pass “this” as a parameter to another function in javascript" --- yes you can, it is an ordinary variable with a reference to current object.

What is a parameterized property?

Based on this principle, the most common use case for parameterized properties in VB.NET is in classes that represent a sequence of items, where the parameter is used to access a specific element in the sequence by its position or by some unique key. In other words, to create what's known as indexers in C#.

What does it mean to pass a parameter by reference?

Passing by reference enables function members, methods, properties, indexers, operators, and constructors to change the value of the parameters and have that change persist in the calling environment. To pass a parameter by reference with the intent of changing the value, use the ref, or out keyword.

How do you pass arguments to parameters in C?

In C#, arguments can be passed to parameters either by value or by reference. Passing by reference enables function members, methods, properties, indexers, operators, and constructors to change the value of the parameters and have that change persist in the calling environment.

How to change parameters at runtime in XAML?

In addition, parameters can be changed at runtime by specifying a new attached property value. An attached property is a special type of bindable property, defined in one class but attached to other objects, and recognizable in XAML as attributes that contain a class and a property name separated by a period.

Is it possible to pass a property by ref?

The challenge is to pass a property by ref. However, properties are translated into two functions when compiled: the get function and set function. Unlike other functions, the framework does not expose these functions at design time which limits one from accessing their delegates. Thanks to Alois Kraus for the original idea:


2 Answers

You almost have the right solution already - the only missing piece is how you use the MeritFunctionLine.property property to get the desired value from the CalculationOutput.

In your foreach loop, simply replace the calculation line with

m += Math.Abs(item.property(values) - item.value);

Edit:

Adding Genericity

To address Obsidian Phoenix's comment, you can use this with different classes by making both MeritFunction and MeritFunctionLine generic, so:

public class MeritFunctionLine<TCalcOutput>
{
    public Func<TCalcOutput, double> property { get; set; }
    public double value { get; set; }
    public ComparisonTypes ComparisonType { get; set; }
}

public class MeritFunction<TCalcOutput>
{
    public List<MeritFunctionLine<TCalcOutput>> Lines { get; set; }
    public double Calculate(TCalcOutput values)
    {
        double m = 0;
        foreach (var item in Lines)
        {
            m += Math.Abs(item.property(values) - item.value);
        }
        return m;
    }
}

The rewritten usage example would be

MeritFunction<CalculationOutput> mf = new MeritFunction<CalculationOutput>();
mf.Lines.Add(new MeritFunctionLine<CalculationOutput>() { property = x => x.Property1, value = 90, comparisonType = ComparisonTypes.GreaterThan });
mf.Lines.Add(new MeritFunctionLine<CalculationOutput>() { property = x => x.Property3, value = 50, comparisonType = ComparisonTypes.Equals });

CalculationOutput c1 = new CalculationOutput() { property1 = 1, property2 = 20, property3 = 150, property4 = 500 };
CalculationOutput c2 = new CalculationOutput() { property1 = 15, property2 = 32, property3 = 15, property4 = 45 };

double value1 = mf.Calculate(c1);
double value2 = mf.Calculate(c2);

Some extra convenience

If you have many MeritFunctionLines to add, the syntax above can be a bit tedious. So as a bonus, let's change MeritFunction so that it can be initialized with the list initialization syntax. To do that, we need to make it IEnumerable and give it an Add function:

public class MeritFunction<TCalcOutput> : IEnumerable<MeritFunctionLine<TCalcOutput>>
{
    public List<MeritFunctionLine<TCalcOutput>> Lines { get; set; }

    public MeritFunction()
    {
        Lines = new List<MeritFunctionLine<TCalcOutput>>();
    }

    public void Add(Func<TCalcOutput, double> property, ComparisonTypes ComparisonType, double value)
    {
        Lines.Add(new MeritFunctionLine<CalculationOutput>
        {
            property = property,
            value = value,
            comparisonType = ComparisonType
        });
    }

    public double Calculate(TCalcOutput values)
    {
        double m = 0;
        foreach (var item in Lines)
        {
            m += Math.Abs(item.property(values) - item.value);
        }
        return m;
    }

    public IEnumerator<MeritFunctionLine<TCalcOutput>> GetEnumerator()
    {
        return List.GetEnumerator();
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Note that the Add method receives the parameters in a different order - you'll understand why when you look at the usage. Quite a bit of extra code, but now creating our MeritFunction is a bit nicer:

MeritFunction<CalculationOutput> mf = new MeritFunction<CalculationOutput>
{
    { x => x.Property1, ComparisonTypes.GreaterThan, 90 },
    { x => x.Property3, ComparisonTypes.Equals,      50 }
};

Note, all code untested. Use at own risk :)

like image 119
Medo42 Avatar answered Oct 08 '22 15:10

Medo42


It is possible, but it's not exactly pretty. You can make use of Expression<Func<double>> to pass in the property, then use reflection to pull the value back out.

NB: I have not coded this to accomodate for error scenarios, you may want to add additional checks.

class Program
{
    static void Main(string[] args)
    {
        MeritFunction<CalculationOutput> mf = new MeritFunction<CalculationOutput>();

        //Create an instance of the object for reference.
        var obj = new CalculationOutput();

        //Use Lambda to set the Property Expression on the Line, pointing at the Property we are interested in.
        mf.Lines.Add(new MeritFunctionLine() { PropertyExpression = () => obj.property1, value = 90, ComparisonType = ComparisonTypes.GreaterThan });
        mf.Lines.Add(new MeritFunctionLine() { PropertyExpression = () => obj.property3, value = 50, ComparisonType = ComparisonTypes.Equals });

        CalculationOutput c1 = new CalculationOutput() { property1 = 1, property2 = 20, property3 = 150, property4 = 500 };
        CalculationOutput c2 = new CalculationOutput() { property1 = 15, property2 = 32, property3 = 15, property4 = 45 };

        double value1 = mf.Calculate(c1);
        double value2 = mf.Calculate(c2);

        Console.WriteLine(value1);
        Console.WriteLine(value2);
    }
}

public class MeritFunctionLine
{
    //Capture an expression representing the property we want.
    public Expression<Func<double>> PropertyExpression { get; set; }

    public double value { get; set; }
    public ComparisonTypes ComparisonType { get; set; }
}

public class MeritFunction<T>
{
    public List<MeritFunctionLine> Lines { get; set; }

    public MeritFunction()
    {
        Lines = new List<MeritFunctionLine>();
    }

    public double Calculate(T values)
    {
        double m = 0;
        foreach (var item in Lines)
        {
            //Get the Value before calculating.
            double value = ExtractPropertyValue(item, values);

            m += Math.Abs(value - item.value);
        }
        return m;
    }

    /// <summary>
    /// Take the Provided Expression representing the property, and use it to extract the property value from the object we're interested in.
    /// </summary>
    private double ExtractPropertyValue(MeritFunctionLine line, T values)
    {
        var expression = line.PropertyExpression.Body as MemberExpression;
        var prop = expression.Member as PropertyInfo;

        double value = (double)prop.GetValue(values);

        return value;
    }
}

public class CalculationOutput
{
    public double property1 { get; set; }
    public double property2 { get; set; }
    public double property3 { get; set; }
    public double property4 { get; set; }
}

public enum ComparisonTypes
{
    GreaterThan,
    Equals
}

The one gotcha of this method, is that you need to create an instance of the object whilst building up the Lines property, otherwise you can't actually access the property through the lambda.

If you only need this for a single class, then this is likely overkill, but it will work with essentially any class.

like image 29
Obsidian Phoenix Avatar answered Oct 08 '22 16:10

Obsidian Phoenix