我能否将C#字符串值转换为转义字符串字面量?

228
在C#中,我可以将字符串值转换为字符串字面值吗?即像在代码中看到的那样?我想用它们的转义序列替换制表符、换行符等。
如果这段代码:
Console.WriteLine(someString);

生成:

Hello
World!

我想要这段代码:

Console.WriteLine(ToLiteral(someString));

生成:

\tHello\r\n\tWorld!\r\n
16个回答

207

很久以前,我发现了这个:

private static string ToLiteral(string input)
{
    using (var writer = new StringWriter())
    {
        using (var provider = CodeDomProvider.CreateProvider("CSharp"))
        {
            provider.GenerateCodeFromExpression(new CodePrimitiveExpression(input), writer, null);
            return writer.ToString();
        }
    }
}

这段代码:

var input = "\tHello\r\n\tWorld!";
Console.WriteLine(input);
Console.WriteLine(ToLiteral(input));

输出:

    Hello
    World!
"\tHello\r\n\tWorld!"

最近,Graham 发现你可以在 NuGet 上使用 Roslyn 的 Microsoft.CodeAnalysis.CSharp 包:

private static string ToLiteral(string valueTextForCompiler)
{
    return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(valueTextForCompiler, false);
}

1
刚在谷歌上找到这个主题。这一定是最好的选择,没有必要重新发明 .net 已经为我们做好的事情。 - Andy Morris
20
不错,但请注意对于更长的字符串,它会插入“+”操作符、换行和缩进。我找不到关闭这个功能的方法。 - Timwi
4
反过来呢?如果你有一个包含转义序列的文本文件,其中包括使用ASCII代码转义的特殊字符,该如何生成原始版本?请问怎么做?(请注意不要改变原意) - Luciano
1
我的周末项目:在MVC表单上实现此例程。如果您只需要偶尔这样做,可以访问我的页面http://csharpstringescape.apphb.com/(代码在Github上)。 - JoshRivers
6
有没有办法使其输出与原始代码中相同的文本(@"...")? - rookie1024
显示剩余8条评论

49

使用 Regex.Escape(String):

Regex.Escape 将一组最小字符((、*、+、?、|、{、[、]、}、)、^、$、.、# 和空格)通过将它们替换为它们的转义码来进行转义。


8
+1 不知道为什么这个回答排名这么低。其他的回答都太啰嗦并且看起来像是在重新发明轮子。 - Adriano Carneiro
58
这不是OP所要求的。它并没有返回一个字符串字面量,而是返回了一个带有正则表达式特殊字符转义的字符串。这会将 Hello World? 转换为 Hello World\?,但那不是一个有效的字符串字面量。 - atheaos
4
我同意@atheaos的观点,这是对一个非常不同问题的很好回答。 - hypehuman
7
+1即使它并没有完全回答提问者的问题,但当我看到这个问题时,它正是我(以及可能其他人)正在寻找的东西。 :) - GazB
1
这样做不会按预期工作。正则表达式的特殊字符不同。例如,\n 可以工作,但是当你有一个空格时,它将被转换为“\”,这不是 C# 的行为... - Ernesto

39

在 NuGet 上的 RoslynMicrosoft.CodeAnalysis.CSharp 包中有一种方法可以实现此功能:

private static string ToLiteral(string valueTextForCompiler)
{
    return Microsoft.CodeAnalysis.CSharp.SymbolDisplay.FormatLiteral(valueTextForCompiler, false);
}

显然,在原问题提出时,此功能并不存在,但它可能会帮助通过谷歌搜索到达此处的人们。


1
这是从 .NET Core 中实现的一种不错的方式。 - Dylan Hayes
1
是的,该软件包支持.NET Core和.NET Standard 2.0 - 这意味着它也可以从.NET Framework 4.6.1+中引用。 - Graham
4
同样适用于源代码生成器。 - Cine
这是我找到的唯一一种转义所有字符的方法。 - Dan
1
工作得很好,但我需要一种方法来恢复原始的未转义字符串。 - Christopher J. Grace

31

这是一个完全工作的实现,包括对Unicode和ASCII不可打印字符的转义。它不像Hallgrim's answer一样插入"+"符号。

static string ToLiteral(string input) {
    StringBuilder literal = new StringBuilder(input.Length + 2);
    literal.Append("\"");
    foreach (var c in input) {
        switch (c) {
            case '\"': literal.Append("\\\""); break;
            case '\\': literal.Append(@"\\"); break;
            case '\0': literal.Append(@"\0"); break;
            case '\a': literal.Append(@"\a"); break;
            case '\b': literal.Append(@"\b"); break;
            case '\f': literal.Append(@"\f"); break;
            case '\n': literal.Append(@"\n"); break;
            case '\r': literal.Append(@"\r"); break;
            case '\t': literal.Append(@"\t"); break;
            case '\v': literal.Append(@"\v"); break;
            default:
                // ASCII printable character
                if (c >= 0x20 && c <= 0x7e) {
                    literal.Append(c);
                // As UTF16 escaped character
                } else {
                    literal.Append(@"\u");
                    literal.Append(((int)c).ToString("x4"));
                }
                break;
        }
    }
    literal.Append("\"");
    return literal.ToString();
}

请注意,这也会转义所有Unicode字符。如果您的环境支持它们,您可以更改该部分以仅转义控制字符:
// UTF16 control characters
} else if (Char.GetUnicodeCategory(c) == UnicodeCategory.Control) {
    literal.Append(@"\u");
    literal.Append(((int)c).ToString("x4"));
} else {
    literal.Append(c);
}

3
你应该使用 Char.GetUnicodeCategory(c) == UnicodeCategory.Control 来决定是否需要转义,否则不会说ASCII的人可能会很不开心。 - deerchao
这取决于情况,如果您的结果字符串将在支持Unicode的环境中使用还是不支持。 - Smilediver
1
我在方法的第一行添加了 input = input ?? string.Empty;,这样我可以传递 null 并得到 "" 而不是空引用异常。 - Andy
为什么你要转义 ',因为这并不是必要的? - trinalbadger587
@Smilediver,你应该编辑你的答案。 - trinalbadger587
显示剩余2条评论

26

更加结构化的方法,包括所有stringchar的转义序列,如下:

它不会用字面等效的方式替换Unicode字符。它也不会煮鸡蛋。

public class ReplaceString
{
    static readonly IDictionary<string, string> m_replaceDict
        = new Dictionary<string, string>();

    const string ms_regexEscapes = @"[\a\b\f\n\r\t\v\\""]";

    public static string StringLiteral(string i_string)
    {
        return Regex.Replace(i_string, ms_regexEscapes, match);
    }

    public static string CharLiteral(char c)
    {
        return c == '\'' ? @"'\''" : string.Format("'{0}'", c);
    }

    private static string match(Match m)
    {
        string match = m.ToString();
        if (m_replaceDict.ContainsKey(match))
        {
            return m_replaceDict[match];
        }

        throw new NotSupportedException();
    }

    static ReplaceString()
    {
        m_replaceDict.Add("\a", @"\a");
        m_replaceDict.Add("\b", @"\b");
        m_replaceDict.Add("\f", @"\f");
        m_replaceDict.Add("\n", @"\n");
        m_replaceDict.Add("\r", @"\r");
        m_replaceDict.Add("\t", @"\t");
        m_replaceDict.Add("\v", @"\v");

        m_replaceDict.Add("\\", @"\\");
        m_replaceDict.Add("\0", @"\0");

        //The SO parser gets fooled by the verbatim version
        //of the string to replace - @"\"""
        //so use the 'regular' version
        m_replaceDict.Add("\"", "\\\"");
    }

    static void Main(string[] args){

        string s = "here's a \"\n\tstring\" to test";
        Console.WriteLine(ReplaceString.StringLiteral(s));
        Console.WriteLine(ReplaceString.CharLiteral('c'));
        Console.WriteLine(ReplaceString.CharLiteral('\''));

    }
}

这不是所有的转义序列 ;) - TcKs
1
比上面的解决方案更好用 - 而且其他转义序列可以轻松添加。 - Arno Peters
被接受的答案中的逐字逐句让我疯狂。这对我的目的来说百分之百有效。用@"[\a\b\f\n\r\t\v\\""/]"替换了正则表达式,并为JSON添加了m_replaceDict.Add("/", @"\/"); - interesting-name-here
此外,如果您想要这些内容,请添加引号。 - interesting-name-here

21

尝试:

var t = HttpUtility.JavaScriptStringEncode(s);

不起作用。如果我有“abc\n123”(不带引号,8个字符),我想要“abc”+ \n +“123”(7个字符)。但它却生成了“abc”+“\”+“\n123”(9个字符)。请注意反斜杠被加倍了,并且它仍然包含一个字符串文字“\n”,而不是转义字符。 - Paul
2
@Paul,你想要的与问题所问的相反。根据你的描述,这个回答了问题,因此是有效的。 - anon
我发现在前端中转义活动目录名称非常有用。 - chakeda

19
public static class StringHelpers
{
    private static Dictionary<string, string> escapeMapping = new Dictionary<string, string>()
    {
        {"\"", @"\\\"""},
        {"\\\\", @"\\"},
        {"\a", @"\a"},
        {"\b", @"\b"},
        {"\f", @"\f"},
        {"\n", @"\n"},
        {"\r", @"\r"},
        {"\t", @"\t"},
        {"\v", @"\v"},
        {"\0", @"\0"},
    };

    private static Regex escapeRegex = new Regex(string.Join("|", escapeMapping.Keys.ToArray()));

    public static string Escape(this string s)
    {
        return escapeRegex.Replace(s, EscapeMatchEval);
    }

    private static string EscapeMatchEval(Match m)
    {
        if (escapeMapping.ContainsKey(m.Value))
        {
            return escapeMapping[m.Value];
        }
        return escapeMapping[Regex.Escape(m.Value)];
    }
}

2
为什么字典的第一个值有三个反斜杠和两个引号? - James Yeoman
很好的回答,@JamesYeoman,那是因为正则表达式模式需要转义。 - Ali Kherad

19

Hallgrim的答案非常好,但是加号、换行和缩进会破坏我的功能。一个简单的解决方法是:

private static string ToLiteral(string input)
{
    using (var writer = new StringWriter())
    {
        using (var provider = CodeDomProvider.CreateProvider("CSharp"))
        {
            provider.GenerateCodeFromExpression(new CodePrimitiveExpression(input), writer, new CodeGeneratorOptions {IndentString = "\t"});
            var literal = writer.ToString();
            literal = literal.Replace(string.Format("\" +{0}\t\"", Environment.NewLine), "");
            return literal;
        }
    }
}

非常好用。我还在return literal之前添加了一行代码,以使其更易读:literal = literal.Replace("\\r\\n", "\\r\\n\"+\r\n\""); - Bob
为了实现JSON功能,添加了这个literal = literal.Replace("/", @"\/"); - interesting-name-here
这是100%直截了当,唯一正确的答案!所有其他答案要么没有理解问题,要么重新发明了轮子。 - bytecode77
很遗憾,在DOTNET CORE下无法使其正常工作。有没有更好的答案? - s k

10

这是对Smilediver的回答的一点改进。它不会转义所有非ASCII字符,而只会转义真正需要的字符。

using System;
using System.Globalization;
using System.Text;

public static class CodeHelper
{
    public static string ToLiteral(this string input)
    {
        var literal = new StringBuilder(input.Length + 2);
        literal.Append("\"");
        foreach (var c in input)
        {
            switch (c)
            {
                case '\'': literal.Append(@"\'"); break;
                case '\"': literal.Append("\\\""); break;
                case '\\': literal.Append(@"\\"); break;
                case '\0': literal.Append(@"\0"); break;
                case '\a': literal.Append(@"\a"); break;
                case '\b': literal.Append(@"\b"); break;
                case '\f': literal.Append(@"\f"); break;
                case '\n': literal.Append(@"\n"); break;
                case '\r': literal.Append(@"\r"); break;
                case '\t': literal.Append(@"\t"); break;
                case '\v': literal.Append(@"\v"); break;
                default:
                    if (Char.GetUnicodeCategory(c) != UnicodeCategory.Control)
                    {
                        literal.Append(c);
                    }
                    else
                    {
                        literal.Append(@"\u");
                        literal.Append(((ushort)c).ToString("x4"));
                    }
                    break;
            }
        }
        literal.Append("\"");
        return literal.ToString();
    }
}

8
有趣的问题。
如果您找不到更好的方法,您总是可以进行替换。
如果您选择这样做,您可以使用此C#转义序列列表
  • \' - 单引号,用于字符文字
  • \" - 双引号,用于字符串文字
  • \\ - 反斜杠
  • \0 - Unicode字符0
  • \a - 警报(字符7)
  • \b - 退格键(字符8)
  • \f - 换页符(字符12)
  • \n - 新行(字符10)
  • \r - 回车(字符13)
  • \t - 水平制表符(字符9)
  • \v - 垂直引号(字符11)
  • \uxxxx - 具有十六进制值xxxx的字符的Unicode转义序列
  • \xn[n][n][n] - 具有十六进制值nnnn的字符的Unicode转义序列(\uxxxx的可变长度版本)
  • \Uxxxxxxxx - 具有十六进制值xxxxxxxx的字符的Unicode转义序列(用于生成代理项)
此列表可在C#常见问题中找到 有哪些字符转义序列可用?

3
这个链接已经失效了,这是为什么只提供链接答案不被鼓励的典型例子。 - James
1
非常正确,@James,但多亏了Jamie Twells,这些信息现在又可以使用了:+1: - Nelson Reis

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