8

有没有办法使用 .net 2.0 以类似于 javascript 中的 eval() 或在 tsql 中使用 sp_executeSQL 的方式动态执行包含在字符串中的代码?

我的变量中有一个字符串值,我想在我的应用程序中的某个时间点进行操作 - 所以代码本质上是字符串操作。我不知道需要什么不同的操作,所以我希望它们是可配置的。

我真的不在乎动态代码是用什么语言编写的,无论哪种语言最容易实现且编写起来足够简单。

例如,我可能想替换 '.' 的实例。带有'-'的字符,或去掉所有空格,或类似的。如果我在 sql 中执行此操作,我会使用动态 sql,但我想在 .net 代码中执行它,如下所示:

// Get the value to be manipulated
string s = ... // wherever s comes from

// Get the manipulation code, eg this might come from a database 
// setting that can be changed without recompiling the .net code.
string manipulation = Settings.GetSomeValue("ManipulationSetting");

// This is what I want to know how to do: apply some manipulation to the string.
string result = MagicDynamicEvalClass.Eval(manipulation, s);

// Now I would do stuff with the result.

我可能只使用正则表达式查找/替换表达式。由于我所做的只是字符串操作,只要我能编写足够聪明的正则表达式就足够了。例如:

// Get the value to be manipulated
string s = ... // wherever s comes from

// Get the code to use to manipulate s, eg this might come from a database 
// setting that can be changed without recompiling the .net code.
string findRegex = Settings.GetSomeValue("RegexPattern");
string replaceRegex = Settings.GetSomeValue("RegexReplace");

// This is what I want to know how to do: apply some manipulation to the string.
string result = Regex.Replace(s, findRegex, replaceRegex);

// Now I can do stuff with the result.

但在某些情况下,我的操作要求可能超出了正则表达式的可能,或者我可能想要应用多个步骤,例如替换“。” 带“-”,还去掉空格。也许我可以存储一个查找/替换正则表达式列表并对其进行迭代......但有人有更好的建议吗?

更新 - 使用动态 sql 的示例

我不想要一个需要我事先知道可以进行哪些操作的解决方案,我真的在寻找简单的东西。例如在 sql 我会做这样的事情:

declare @s nvarchar(1000)
declare @manipulation nvarchar(1000)
declare @result nvarchar(1000)

-- ... Get the values from wherever they come from

-- Execute the manipulation dynamically
EXEC sp_ExecuteSQL @stmt = @manipulation
    , @params = N'@s nvarchar(1000), @result nvarchar(1000) OUTPUT'
    , @s = @s, @result = @result OUTPUT

然后我可以将任意 sql 放入我的@manipulation 中,就像这样 SET @result = REPLACE(REPLACE(@s, '.', '-'), ' ', '' )

是的,这需要我注意允许将哪些值放入@manipulation,但这会给我未来需要的灵活性。

我猜想在 javascript 中使用 eval() 也可以使用类似的方法。

更新 - 使用 .net 中的 MSScript 控件的示例:

似乎是一种可能的方法,尽管对于我想要处理的简单案例来说可能有点矫枉过正。它使用 Microsoft Script Control 库来允许执行任意 VBScript。

4

8 回答 8

14

这不是太难;)我整理了一个小例子。这应该可以帮助您决定是否要使用动态脚本.. 或正则表达式。

您可以做的是在您的程序集中创建一个接口,您的动态代码将实现该接口:

namespace CompileScriptExample
{
  public interface IStringManipulator
  {
    string processString(string aString);
  }
}

然后创建一个 ScriptRunner 类:

namespace CompileScriptExample
{ 
public class ScriptRunner
{

    public static string RunScript(string scriptCode, string scriptParameter)
    {

        CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider();

        //configure parameters
        CompilerParameters parameters = new CompilerParameters();
        parameters.GenerateExecutable = false;
        parameters.GenerateInMemory = true;
        parameters.IncludeDebugInformation = false;
        string reference;
        // Set reference to current assembly - this reference is a hack for the example..
        reference = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
        parameters.ReferencedAssemblies.Add(reference+"\\CompileScriptExample.exe");

        //compile
        CompilerResults results = provider.CompileAssemblyFromSource(parameters, new string[] { scriptCode });

        if (results.Errors.Count == 0)
        {
            IStringManipulator compiledScript=(IStringManipulator)FindInterface(results.CompiledAssembly, "IStringManipulator");
            return compiledScript.processString(scriptParameter);//run the script, pass the string param..
        }
        else
        {
            foreach(CompilerError anError in results.Errors)
            {
                MessageBox.Show(anError.ErrorText);
            }
            //handle compilation errors here
            //..use results.errors collection
            throw new Exception("Compilation error...");
        }
    }

    private static object FindInterface(Assembly anAssembly, string interfaceName)
    {
        // find our interface type..
        foreach (Type aType in anAssembly.GetTypes())
        {
            if (aType.GetInterface(interfaceName, true) != null)
                return anAssembly.CreateInstance(aType.FullName);
        }
        return null;
    }
}

}

现在您所要做的就是使用实现您的接口的代码创建一个脚本字符串,例如..

string myScriptString=@"using CompileScriptExample;
public class MyStringManipulator : IStringManipulator
{
  public string processString(string aString)
  {
        return aString+aString;
  }
};

然后.. 在您的代码中,使用 ScriptRunner 用您的动态代码处理您的字符串:

string processedString = ScriptRunner.RunScript(myScriptString, "hello");
于 2009-02-04T04:31:38.317 回答
5

我知道你在 C# 之后,但我的代码是在 VB 中。您可以使用 Developer Fusion 的 VB 到 C# 转换器轻松地将其翻译。我在一个项目中使用它来允许用户在运行时将复杂的计算添加到他们的应用程序中。它将他们的 VB 代码编译到内存中的库中,然后运行返回结果输出的代码。它可以很容易地重新用于您尝试做的事情。

Imports System.Reflection
Imports System.CodeDom.Compiler
Imports System.Text.RegularExpressions
Imports System.Math

Module Module1

  Function Evaluate(ByVal Expression As String, ByVal Args() As Object) As Object

    If Expression.Length > 0 Then

        'Replace each parameter in the calculation expression with the correct values
        Dim MatchStr = "{(\d+)}"
        Dim oMatches = Regex.Matches(Expression, MatchStr)
        If oMatches.Count > 0 Then
            Dim DistinctCount = (From m In oMatches _
                                 Select m.Value).Distinct.Count
            If DistinctCount = Args.Length Then
                For i = 0 To Args.Length - 1
                    Expression = Expression.Replace("{" & i & "}", Args(i))
                Next
            Else
                Throw New ArgumentException("Invalid number of parameters passed")
            End If
        End If

        Dim FuncName As String = "Eval" & Guid.NewGuid.ToString("N")
        Dim FuncString As String = "Imports System.Math" & vbCrLf & _
                                   "Namespace EvaluatorLibrary" & vbCrLf & _
                                   "  Class Evaluators" & vbCrLf & _
                                   "    Public Shared Function " & FuncName & "() As Double" & vbCrLf & _
                                   "      " & Expression & vbCrLf & _
                                   "    End Function" & vbCrLf & _
                                   "  End Class" & vbCrLf & _
                                   "End Namespace"

        'Tell the compiler what language was used
        Dim CodeProvider As CodeDomProvider = CodeDomProvider.CreateProvider("VB")

        'Set up our compiler options...
        Dim CompilerOptions As New CompilerParameters()
        With CompilerOptions
            .ReferencedAssemblies.Add("System.dll")
            .GenerateInMemory = True
            .TreatWarningsAsErrors = True
        End With

        'Compile the code that is to be evaluated
        Dim Results As CompilerResults = _
            CodeProvider.CompileAssemblyFromSource(CompilerOptions, FuncString)

        'Check there were no errors...
        If Results.Errors.Count > 0 Then
        Else
            'Run the code and return the value...
            Dim dynamicType As Type = Results.CompiledAssembly.GetType("EvaluatorLibrary.Evaluators")
            Dim methodInfo As MethodInfo = dynamicType.GetMethod(FuncName)
            Return methodInfo.Invoke(Nothing, Nothing)
        End If

    Else
        Return 0

    End If

    Return 0

  End Function

End Module

我这样设置我的动态代码:

Dim Expr As String = "  If ({0} < 20000) Then" & vbCrLf & _
                     "    Return Max(15, Min(75,0.12*{0}))" & vbCrLf & _
                     "  Else" & vbCrLf & _
                     "    Return Max(75,0.05*{0})" & vbCrLf & _
                     "  End If"

然后为表达式设置一些参数并执行:

Dim Args As New List(Of String)
While True
    Dim Val As String = Console.ReadLine
    Args.Clear()
    If IsNumeric(Val) Then
        Args.Add(Val)
        Dim dblOut As Object = Evaluate(Expr, Args.ToArray)
        Console.WriteLine(dblOut)
    Else
        Exit While
    End If
End While
于 2009-02-03T20:26:15.457 回答
1

我认为可以使用反射.emit 和 codedom 来做到这一点,但这一点也不简单,我建议不要这样做。

作为替代方案,您可以尝试配置格式字符串,可能除了正则表达式。

于 2009-02-03T18:08:01.820 回答
1

就像其他人已经提到的那样,在 eval() 函数中编译 c# 是不可能的。该功能计划用于安德斯在 PDC 上演示的 clr 的后续版本。

作为一个不同的解决方案,如果您的应用程序能够在单声道上运行,您可以使用它的 eval 函数,它可以动态编译 c# 代码,就像 javascript 一样。它基本上已经在做 .net 在一两年内能够做到的事情。

作为替代方案,如果您不能使用单声道,您可以编写在具有 eval() 的 Ironruby 中进行字符串操作的部分。您的其余代码甚至不知道您正在为该类/组装使用 ruby​​。

对于这样一个简单的用例,您在更新中发布的链接看起来相当复杂。使用ironruby,您所要做的就是编写类似这样的MyDynamicEvalClass:

class MyDynamicEvalClass
  def eval(someString,transformString)
    eval(someString,transformString)
  end
end

并用一些返回新字符串的红宝石代码替换“ManipulationSetting”

于 2009-02-03T19:52:34.137 回答
1

我的 Eval 解决方案怎么样

如何在动态类型上执行字符串路径?

于 2011-01-26T18:02:23.640 回答
0

虽然您可以使用枚举来指示您要执行的操作,或者使用 CodeDom 动态发出代码,但归根结底是您想要定义某种转换,这意味着您有输入和输出.

在这种情况下,弄清楚输出很容易,你有一个字符串。对于输入,您似乎可以有可变数量的输入。那将被定义为IEnumerable<string>.

考虑到这一点,您可以像这样定义一个接口:

public interface IStringManipulation
{
  string Manipulate(IEnumerable<string> parameters);
}

然后,很容易定义这种类型的实现,然后将类型名称放入您的配置中。

真的想这样做,而不是从字符串动态编译代码。在使用字符串时,您有很大的灵活性,是的,但是您没有编译时检查,并且正在向错误和安全问题敞开大门。

此外,编写一段代码以根据您提供的字符串片段发出代码所花费的时间也将非常乏味,因为您必须构建程序集、类、方法,然后编译,然后调用你通过反射动态编译的方法(除非你让它实现一个接口,在这种情况下,你还是按照我的建议去做)。

于 2009-02-03T18:29:11.667 回答
0

前几天我刚刚遇到了一些用另一种 .NET 语言执行此操作的东西: http ://reverseblade.blogspot.com/2009/02/dont-wait-for-c-5-use-nemerle.html

我想你可以用你的 C# 或 VB 应用程序在 nemerle 中编写、编译和引用你的字符串处理代码。

我仍然喜欢 casperOne 建议的可扩展设计。通过执行动态脚本,您只需将编译推送到应用程序中并部署到任何将编程字符串发送到应用程序的过程中。但听起来你有你的理由,所以我在这里唯一考虑的另一件事是安全性。理想情况下,您希望尽可能地限制用户操作字符串,在这种情况下,List-of-Regex 似乎是一个不错的选择。

于 2009-02-03T20:27:16.017 回答
0

没有用于在运行时调用 eval() 的 C# 内置方法。

但是,我的 C# eval 程序确实允许评估 C# 代码。它提供了在运行时评估 C# 代码并支持许多 C# 语句。事实上,此代码可用于任何 .NET 项目,但仅限于使用 C# 语法。查看我的网站http://csharp-eval.com了解更多详细信息。

于 2011-06-10T02:51:02.347 回答