开发者

Can I expand a string that contains C# literal expressions at runtime

开发者 https://www.devze.com 2023-01-08 18:06 出处:网络
If I have a string that contains a c# string literal expression can I \"expand\" it at runtime public void TestEvaluateString()

If I have a string that contains a c# string literal expression can I "expand" it at runtime

    public void TestEvaluateString()
    {
        s开发者_高级运维tring 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";
    }

Like Can I convert a C# string value to an escaped string literal, but in reverse?


Similar to Mikael answer but using the 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;
    }

You might be better off using a dictionary of wildcards and just replacing them in the string.


Regex.Unescape would be my method of choice.


Not sure if this is the simplest way, but by referencing the Microsoft.JScript namespace you can reparse it with the javascript eval function.

Here's a test for the code at the bottom

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

This will turn the \r into a carriage return.

And the implementation

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;
              }
           }
        }";
}


If you're just looking to do "simple" escape characters as defined on the Microsoft site, you can use this routine and save importing external libs:

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();
    }
}

Here's a unit test to prove it:

    [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));
    }

Feel free to tweak the EscapeMap or rename the function UnescapeSimple (awkward I know).

Note that this solution doesn't handle Unicode escape characters or hex or octal, it just handles the simple single character ones.


You can achieve this with a one-liner using the Microsoft.CodeAnalysis.CSharp.Scripting package.

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

If you start including the outer quotes in the method argument, the method can be generalized to handle verbatim, interpolated, and concatenated strings too (.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"

This method can be slow to invoke repeatedly. If you have a large number of strings to convert, you'd be better off batching them (.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);
}

As with all dynamic compilation, you need to restrict your calls to trusted input only, or take measures to protect against injection attacks.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号