我可以在运行时扩展包含C#文字表达式的字符串吗?

8
如果我有一个包含C#字符串文字表达式的字符串,我是否可以在运行时"展开"它?
    public void TestEvaluateString()
    {
        string Dummy = EvalString( @"Contains \r\n new line");
        Debug.Assert(Dummy == "Contains \r\n new line");
    }

    private string EvalString(string input)
    {
        return "Contains \r\n new line";
    }

将C#字符串值转换为转义字符串文字一样,但是相反的呢?


你知道你的断言会失败,对吧?而第二个方法并没有使用它的参数。 - H H
1
这是一个Nunit Assert,我已经编辑了代码以使用debug.assert。第二种方法是TDD(测试驱动设计)存根,问题的答案将提供更一般的解决方案。 - Andy Morris
你仍然提供了两个不相关的函数。最后一行帮助我理解,代码只会让我感到困惑。 - H H
5个回答

11

类似于Mikael的答案,但是使用CSharpCodeProvider:

    public static string ParseString(string txt)
    {
        var provider = new Microsoft.CSharp.CSharpCodeProvider();
        var prms = new System.CodeDom.Compiler.CompilerParameters();
        prms.GenerateExecutable = false;
        prms.GenerateInMemory = true;
        var results = provider.CompileAssemblyFromSource(prms, @"
namespace tmp
{
    public class tmpClass
    {
        public static string GetValue()
        {
             return " + "\"" + txt + "\"" + @";
        }
    }
}");
        System.Reflection.Assembly ass = results.CompiledAssembly;
        var method = ass.GetType("tmp.tmpClass").GetMethod("GetValue");
        return method.Invoke(null, null) as string;
    }

你最好使用通配符的字典,然后只需在字符串中替换它们。


使用csharp提供程序的想法很好。我太习惯于使用eval并修改了我已经有的代码,以至于错过了这种明显的做法。 - Mikael Svenson
这对以下内容无效:ParseString("foo\nbar") - Gluip
@Gluip,这不需要是ParseString(@“foo \n bar”)吗?如果不是,ParseString内部的代码就无法编译。不过,如果它能更好地处理这种失败并干净地返回,那就更好了。 - JDunkerley
2
我无法相信唯一的方法是在用户端系统上编译一个DLL文件(只是将其留在临时目录中)。如果这段代码被多次调用,那么速度会相当慢。虽然+1表示答案可行,但我真的希望能看到更好的解决方案。 - Paul

6

谢谢,这正是我为自己的问题所寻找的。 - Rayanth

3

不确定这是否是最简单的方法,但是通过引用 Microsoft.JScript 命名空间,您可以使用javascript eval函数重新解析它。

下面是代码的测试:

var evalToString = Evaluator.MyStr("test \\r\\n test");

这将把 \r 转换成回车符。
实现方式如下:
public class Evaluator
{
    public static object MyStr(string statement)
    {
        return _evaluatorType.InvokeMember(
                    "MyStr",
                    BindingFlags.InvokeMethod,
                    null,
                    _evaluator,
                    new object[] { statement }
                 );
    }

    static Evaluator()
    {
        ICodeCompiler compiler;
        compiler = new JScriptCodeProvider().CreateCompiler();

        CompilerParameters parameters;
        parameters = new CompilerParameters();
        parameters.GenerateInMemory = true;

        CompilerResults results;
        results = compiler.CompileAssemblyFromSource(parameters, _jscriptSource);

        Assembly assembly = results.CompiledAssembly;
        _evaluatorType = assembly.GetType("Evaluator.Evaluator");

        _evaluator = Activator.CreateInstance(_evaluatorType);
    }

    private static object _evaluator = null;
    private static Type _evaluatorType = null;
    private static readonly string _jscriptSource =

        @"package Evaluator
        {
           class Evaluator
           {
              public function MyStr(expr : String) : String 
              { 
                 var x;
                 eval(""x='""+expr+""';"");
                 return x;
              }
           }
        }";
}

2

如果你只是想做“简单”的转义字符,如Microsoft网站所定义的那样,你可以使用这个程序并保存导入外部库:

public static class StringExtensions
{
    /* https://msdn.microsoft.com/en-us/library/aa691087(v=vs.71).aspx */
    private readonly static SortedDictionary<char, char> EscapeMap = new SortedDictionary<char, char>
    {
        { '\'', '\'' },
        { '"', '\"' },
        { '\\', '\\' },
        { '0', '\0' },
        { 'a', '\a' },
        { 'b', '\b' },
        { 'f', '\f' },
        { 'n', '\n' },
        { 'r', '\r' },
        { 't', '\t' },
        { 'v', '\v' },
    };

    public static string UnescapeSimple(this string escaped)
    {
        if (escaped == null)
            return escaped;

        var sb = new StringBuilder();

        bool inEscape = false;
        var s = 0;
        for (var i = 0; i < escaped.Length; i++)
        {
            if (!inEscape && escaped[i] ==  '\\')
            {
                inEscape = true;
                continue;
            }

            if (inEscape)
            {
                char mapChar;
                if (EscapeMap.TryGetValue(escaped[i], out mapChar))
                {
                    sb.Append(escaped.Substring(s, i - s - 1));
                    sb.Append(mapChar);

                    s = i + 1;
                }
                inEscape = false;
            }
        }

        sb.Append(escaped.Substring(s));

        return sb.ToString();
    }
}

这是一个单元测试来证明它:
    [TestMethod]
    public void UnescapeSimpleTest()
    {
        var noEscapes = @"This is a test".UnescapeSimple();
        Assert.AreEqual("This is a test", noEscapes, nameof(noEscapes));

        var singleEscape = @"\n".UnescapeSimple();
        Assert.AreEqual("\n", singleEscape, nameof(singleEscape));

        var allEscape = @"\'\""\\\0\a\b\f\n\r\t\v".UnescapeSimple();
        Assert.AreEqual("\'\"\\\0\a\b\f\n\r\t\v", allEscape, nameof(allEscape));

        var textInEscapes = @"\tthis\n\ris\\a\ntest".UnescapeSimple();
        Assert.AreEqual("\tthis\n\ris\\a\ntest", textInEscapes, nameof(textInEscapes));

        var backslashNoEscapes = @"\,\h\qtest".UnescapeSimple();
        Assert.AreEqual(@"\,\h\qtest", backslashNoEscapes, nameof(backslashNoEscapes));

        var emptyStr = "".UnescapeSimple();
        Assert.AreEqual("", emptyStr, nameof(emptyStr));

        // Prove Enviroment.NewLine is "\r\n" and not "\n\r" (Windows PC)
        var newLine = @"\r\n".UnescapeSimple();
        Assert.AreEqual(Environment.NewLine, newLine, nameof(newLine));

        // Double check prior test (Windows PC)
        var newLineWrong = @"\n\r".UnescapeSimple();
        Assert.AreNotEqual(Environment.NewLine, newLineWrong, nameof(newLineWrong));
    }

随意调整EscapeMap或重命名函数UnescapeSimple(我知道这很尴尬)。

请注意,此解决方案不处理Unicode转义字符、十六进制或八进制,它只处理简单的单个字符。


0

您可以使用Microsoft.CodeAnalysis.CSharp.Scripting包中的一行代码来实现此操作。

private Task<string> EvaluateStringAsync(string input)
{
    return CSharpScript.EvaluateAsync<string>('"' + input + '"');
}

如果您在方法参数中包含外部引号,该方法可以泛化处理逐字、插值和连接字符串(.NET Fiddle):

private Task<string> EvaluateStringAsync(string input)
{
    return CSharpScript.EvaluateAsync<string>(input);
}

// await EvaluateStringAsync(@"$""This is a number: {40:N3}""")
// Output: "This is a number: 40.000"

这种方法在重复调用时可能会很慢。如果您有大量字符串需要转换,最好将它们分批处理(.NET Fiddle):

private static Task<string[]> EvaluateStringsAsync(string[] inputs)
{
    var inputsConcat = string.Concat(inputs.Select(x => $"    {x},\r\n"));
    var arrayInitializationCode = $"new[] {{\r\n{inputsConcat}}}";
    return CSharpScript.EvaluateAsync<string[]>(arrayInitializationCode);
}

与所有动态编译一样,您需要仅限于受信任的输入来限制调用,或采取措施防止注入攻击。


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