我正在创建一个优值函数计算器,对于初学者来说,它会选择一些属性,并根据这些属性与某些理想值(优点函数)的接近程度来计算一个值。然后,这使用户能够找到最符合其要求的项目。
这是我想要使用的代码:
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 的成员,但这里是对我想要做的解释:
即
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#禁止的函数。
答案 0 :(得分:10)
您几乎已经拥有了正确的解决方案 - 唯一缺少的是如何使用MeritFunctionLine.property
属性从CalculationOutput
获取所需的值。
在foreach
循环中,只需将计算行替换为
m += Math.Abs(item.property(values) - item.value);
修改强>
添加通用性
要解决Obsidian Phoenix的评论,您可以通过使MeritFunction
和MeritFunctionLine
通用,将其用于不同的类,因此:
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 :(得分:4)
这是可能的,但它并不完全漂亮。您可以使用Expression<Func<double>>
传递属性,然后使用反射将值拉回来。
NB:我没有对此进行编码以适应错误情况,您可能需要添加其他检查。
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实际访问该属性。
如果你只需要一个单独的课程,那么这可能是矫枉过正,但它基本上适用于任何课程。