问题描述
我正在尝试实现一种解决方案,其中我对一个项目进行了预定义的计算,对于每个单独的项目,此计算可能会有所不同,这就是我计划采用这种方式的原因。
是否可以通过字符串插值传递此计算,同时仍保留对变量的引用?
为了方便阅读,我对此进行了简化,但是原理是相同的,这些是传入的参数:
double individualTotal = 100;
double parentTotal = 2000;
double siblingTotal = 1500;
double anotherTotal = 100;
计算将以文本形式存储在数据库中,例如:
(siblingTotal/parentTotal)*individualTotal
或者另一个计算可能是:
((siblingTotal/(parentTotal)*individualTotal)+(anotherTotal*0.5)
var calculationText = "{(siblingCalculationTotalValue/parentCalculationTotalValue)*individualTotalValue}";
var calculation = $"" + calculationText + ""
并且:
var calculationText = "{(siblingCalculationTotalValue/parentCalculationTotalValue)*individualTotalValue}";
var calculation = $"{calculationText}"
两个输出:
"CalculatedValue": "{(siblingCalculationTotalValue/parentCalculationTotalValue)*individualTotalValue}"
我也尝试过:
var calculationText = "(siblingCalculationTotalValue/parentCalculationTotalValue)*individualTotalValue";
var calculation = $"{calculationText}"
输出:
"CalculatedValue": "(siblingCalculationTotalValue/parentCalculationTotalValue)*individualTotalValue"
当我将参数直接传递到字符串插值时,这可以按预期工作,但是它不允许计算中的可变性:
var calculation = $"{(siblingCalculationTotalValue/parentCalculationTotalValue)*individualTotalValue}"
输出:
"CalculatedValue": "75"
任何帮助将不胜感激!
解决方法
不能将字符串插值存储为变量的原因是,字符串插值在编译时已解析为CIL(几乎可以肯定地解析为String.Concat
)。
出于所有目的和目的,您拥有的任何逻辑与所有其他C#代码完全相同,并且必须进行编译。
问题就变成了,如何在运行时编译任意C#(在本例中为数学方程式/计算)。
警告
以下内容将编译并执行任意C#代码,这是一个巨大的安全漏洞,因为可以输入恶意代码,并且必须格外小心。仅对最终用户影响为零的字符串尝试此操作,或者如果您的应用程序在最终用户的计算机上运行,请向他们解释,他们输入的任何内容都是恶意的。
评估任意C#代码的lambda函数
首先像这样创建ScriptOptions
的实例:
ScriptOptions scriptoptions = ScriptOptions.Default.WithImports("System");
如果需要使用等式/ C#代码的应用程序中存在的任何功能,则需要进行一些修改:
ScriptOptions scriptoptions = ScriptOptions.Default
.WithImports("System","YOURNAMESPACE")
.WithReferences(System.Reflection.Assembly.GetExecutingAssembly());
ScriptOptions
是通用的,可以是静态的,可以重复用于多个评估。
然后您可以像下面那样评估等式/ C#代码:
Func<double,double,double> equation =
CSharpScript.EvaluateAsync<Func<double,double>>(
"(individualTotal,parentTotal,siblingTotal,anotherTotal) => { return " + calculationText + "; }",scriptoptions
).Result;
calculationText
可能是((siblingTotal/(parentTotal)*individualTotal)+(anotherTotal*0.5)
的地方。
equation
:
var calculation = equation(individualTotal,anotherTotal);
内存使用和GC问题
此评估不能确定内存使用情况,最多可以使用500MB内存。它仅用于评估表达式,并标记为CSharpScript.EvaluateAsync
之后的垃圾回收。
如果您看到自己的应用程序使用500MB内存,并且像我开始使用CSharpScript.EvaluateAsync
时一样感到困惑,请放心,知道您的应用程序不是 使用500MB内存,并且已经标记为垃圾回收。
如果这是用户将在其计算机上运行的应用程序,则可以在GC.Collect
之后直接调用CSharpScript.EvaluateAsync
,以强制GC立即清除标记的内存,如下所示:
Func<double,scriptoptions
).Result;
GC.Collect();
,
您可以创建一个返回calc结果的函数,然后可以对其进行插值,还需要将字段值直接存储在数据库中而不是字符串中,以便将这些值传递给该函数。
根据一个基于您的两个计算的示例,我在控制台应用程序中创建了它,只是为了说明它如何工作:
class Program
{
static void Main(string[] args)
{
double individualTotal = 100;
double parentTotal = 2000;
double siblingTotal = 1500;
double anotherTotal = 100;
var test = new Test();
var calculation1 = $"{test.Calcs(CalculatorEnum.Calc1,individualTotal,anotherTotal)}";
var calculation2 = $"{test.Calcs(CalculatorEnum.Calc2,anotherTotal)}";
Console.WriteLine($"Calc 1: {calculation1}");
Console.WriteLine($"Calc 2: {calculation2}");
}
}
我还创建了一个名为Test的类,该方法带有一个方法和一个枚举,以排序此函数应使用的计算形式:
public enum CalculatorEnum
{
Calc1 = 1,Calc2 = 2
}
public class Test
{
public double Calcs(CalculatorEnum calcType,double individualTotal,double parentTotal,double siblingTotal,double anotherTotal)
{
double resp = 0;
switch (calcType)
{
case CalculatorEnum.Calc1:
resp = (siblingTotal / parentTotal) * individualTotal;
break;
case CalculatorEnum.Calc2:
resp = ((siblingTotal / (parentTotal) * individualTotal) + (anotherTotal * 0.5));
break;
}
return resp;
}
}
这些是预期的结果:
$ dotnet run
Calc 1: 75
Calc 2: 125