将属性作为参数传递

本文关键字:参数传递 属性 | 更新日期: 2023-09-27 18:17:42

我正在创建一个价值函数计算器,对于没有经验的人来说,它接受属性的选择,并根据这些属性与一些理想值(价值函数)的接近程度计算一个值。这样用户就可以找到最符合他们需求的商品。

这是我想使用的代码:

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; }
}

显然,这不能编译为values不包含名为属性的成员,但这里是我想要做的解释:

  1. 创建一个新的MeritFunction
  2. 在MeritFunction中添加任意数量的meritfunctionline。行
  3. MeritFunctionLine。property应该指定在MeritFunction中比较CalculationOutput的哪个属性。计算

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);

我不是在问如何将属性作为参数传递到函数中,这是c#所禁止的。

将属性作为参数传递

您几乎已经有了正确的解决方案-唯一缺少的部分是如何使用MeritFunctionLine.property属性从CalculationOutput获得所需的值。

foreach循环中,只需将计算行替换为

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

添加Genericity

为了解决Obsidian Phoenix的评论,您可以通过将MeritFunctionMeritFunctionLine设置为通用类型来将其用于不同的类,因此:

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;
    }
}

重写后的用法示例是

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);

一些额外的方便

如果您有许多MeritFunctionLine要添加,那么上面的语法可能有点乏味。因此,作为额外的奖励,让我们更改MeritFunction,以便可以使用列表初始化语法对其进行初始化。要做到这一点,我们需要将其设置为IEnumerable,并给它一个Add函数:

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();
    }
}

请注意,Add方法以不同的顺序接收参数-当您查看用法时您将了解原因。相当多的额外代码,但现在创建我们的MeritFunction是有点好:

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

注意,所有代码都未经测试。使用风险自负:)

这是可能的,但它并不完全漂亮。您可以使用Expression<Func<double>>传递属性,然后使用反射将值拉出来。

注意:我没有编写这个代码以适应错误场景,您可能需要添加额外的检查。

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
}

该方法的一个缺点是,在构建Lines属性时需要创建对象的实例,否则无法通过lambda实际访问该属性。

如果您只需要在单个类中使用此方法,那么这可能是多余的,但它实际上可以用于任何类。