传递属性作为参数

11

我正在创建一个优点函数计算器,对于不熟悉的人来说,它会选择一些属性,并根据这些属性与某些理想值的接近程度(即优点函数)来计算一个值。这使用户可以找到最符合他们要求的项目。

这是我想使用的代码:

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中不存在名为property的成员,但是这里解释一下我的意图:

  1. 创建一个新的MeritFunction。
  2. 向MeritFunction.Lines中添加任意数量的MeritFunctionLines。
  3. MeritFunctionLine.property应指定在MeritFunction.Calculate中比较的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#中是被禁止的。


6
嘿,这在C#中是被禁止的! :) - Marcel N.
10
如果你在foreach循环中使用m += Math.Abs(item.property(values) - item.value);,就我理解的来看,我并不太清楚问题出在哪里,但这应该可以正常工作。请注意,此处为非官方翻译,仅供参考。 - Medo42
不清楚您想如何处理 comparisonType - sWW
2
在这种情况下,我认为Mendo42的评论应该能帮助你。 - sWW
@Medo42,谢谢你的帮助,对我来说最后一步是将其泛型化! - Nick
显示剩余6条评论
2个回答

10
你已经接近正确的解决方案了 - 唯一缺失的是如何使用 MeritFunctionLine.property 属性从 CalculationOutput 中获取所需值。
在你的 foreach 循环中,只需将计算行替换为
m += Math.Abs(item.property(values) - item.value);

编辑:

添加泛型

为了应对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 }
};

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


1
这绝对比我的示例整洁得多,如果(根据评论)OP想要将此用于各种不同的类,您将如何扩展代码? - Obsidian Phoenix
啊,好的更新。正是我要更新我的答案的内容。;) - Obsidian Phoenix
我觉得加一点奖励会更好 :) - Medo42
我特别喜欢IEnumerable的实现-这是一个明智的补充。 - Nick
这是使列表初始化器语法工作的要求。 - Medo42

4

这是可能的,但不是非常美观。你可以利用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访问该属性。
如果您只需要针对单个类使用此方法,则可能过于复杂,但它可以用于基本上任何类。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接