将计算传递给字符串插值

问题描述

我正在尝试实现一种解决方案,其中我对一个项目进行了预定义的计算,对于每个单独的项目,此计算可能会有所不同,这就是我计划采用这种方式的原因。

是否可以通过字符串插值传递此计算,同时仍保留对变量的引用?

为了方便阅读,我对此进行了简化,但是原理是相同的,这些是传入的参数:

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