C#中针对verbatim字符串的多行格式化(使用@前缀)。

23

我喜欢在C#中使用@“字符串”,尤其是当我有很多多行文本时。唯一的烦恼是,当我这样做时,我的代码格式会变得混乱不堪,因为第二行和更长的行被完全推到了左边,而没有使用我美观格式化代码的缩进。我知道这是设计上的问题,但是否有一些选项/黑科技方式可以允许这些行缩进,而不需要添加实际的制表符/空格到输出中呢?

举个例子:

        var MyString = @" this is 
a multi-line string
in c#.";

我的变量声明缩进到了“正确”的深度,但是字符串中的第二行及其后续行都被推到了左边距-所以代码看起来有点丑陋。你可以在第2行和第3行开头添加制表符,但是字符串本身将包含这些制表符...明白我的意思吗?


2
我通常会在字符串前单独一行开始(即在 @ 之前换行),这样至少不会突然从右边跳到左边。不过我知道这并不是你想要的解决方案。 - George Duckett
1
真正的问题应该是,如果它使你的源代码更美观,那么是否值得额外处理字符串? - docmanhattan
我因这一个问题而获得了一个受欢迎的徽章。这让我有点尴尬;) - Brady Moritz
1
我被Python的textwrap.dedent()函数宠坏了,现在在寻找C#的等价物。 - aaaantoine
请查看(并点赞)我对Visual Studio IDE的更改建议:缩进多行逐字字符串 - Olivier Jacot-Descombes
显示剩余5条评论
5个回答

14

如何使用字符串扩展?更新:我重新阅读了您的问题,希望有更好的答案。对我来说,这也是一个烦恼的问题,必须像下面解决它非常令人沮丧,但是它确实有效。

using System.Text.RegularExpressions;

namespace ConsoleApplication1
{
    public static class StringExtensions
    {
        public static string StripLeadingWhitespace(this string s)
        {
            Regex r = new Regex(@"^\s+", RegexOptions.Multiline);
            return r.Replace(s, string.Empty);
        }
    }
}

以下是一个示例控制台程序:

using System;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string x = @"This is a test
                of the emergency
                broadcasting system.";

            Console.WriteLine(x);

            Console.WriteLine();
            Console.WriteLine("---");
            Console.WriteLine();

            Console.WriteLine(x.StripLeadingWhitespace());

            Console.ReadKey();
        }
    }
}

输出结果:

This is a test
                of the emergency
                broadcasting system.

---

This is a test
of the emergency
broadcasting system.

如果您决定采用这种方法,使用它的更清晰方式:

string x = @"This is a test
    of the emergency
    broadcasting system.".StripLeadingWhitespace();
// consider renaming extension to say TrimIndent() or similar if used this way

是的,这是我考虑过的一个解决方案。可能需要添加智能功能,只去除特定数量的空格或制表符,因为有可能你希望在某些行上保留空格或制表符 ;) - Brady Moritz
也许可以检查字符串是否以任何空格开头,并在这种情况下默认将 ^\s+ 替换为该数量的空格? - Cymen

7

Cymen已经给出了正确的解决方案。我使用了类似于Scala中stripMargin()方法的方法。下面是我的扩展方法:

public static string StripMargin(this string s)
{
    return Regex.Replace(s, @"[ \t]+\|", string.Empty);
}

使用方法:

var mystring = @"
        |SELECT 
        |    *
        |FROM
        |    SomeTable
        |WHERE
        |    SomeColumn IS NOT NULL"
    .StripMargin();

结果:

SELECT 
    *
FROM
    SomeTable
WHERE
    SomeColumn IS NOT NULL

5

在 C# 11 中,现在您可以使用 原始字符串字面量

var MyString = """
    this is 
    a multi-line string
    in c#.
    """;

输出结果为:

this is
a multi-line string
in c#.

它还可以与字符串插值结合使用:

var variable = 24.3;
var myString = $"""
    this is 
    a multi-line string
    in c# with a {variable}.
    """;

相当不错,对吧? - Brady Moritz

2

我无法想到一个完全能够满足你问题的答案,但是你可以编写一个函数,从包含在字符串中的文本行中去掉前导空格,并在每次创建这样的字符串时调用它。

var myString = TrimLeadingSpacesOfLines(@" this is a 
    a multi-line string
    in c#.");

是的,这是一个hack,但你在问题中明确指定了你接受一个hack。

问题在于 - 在某些情况下,我可能希望有一些前导空格,因此我只想删除足够的空格,使文本达到左边缘...明白吗? - Brady Moritz

1
这里有一个较长的解决方案,尽可能地模仿 textwrap.dedent。第一行保持原样,不需要缩进。(您可以使用 doctest-csharp 基于文档测试生成单元测试。)
/// <summary>
/// Imitates the Python's
/// <a href="https://docs.python.org/3/library/textwrap.html#textwrap.dedent">
/// <c>textwrap.dedent</c></a>.
/// </summary>
/// <param name="text">Text to be dedented</param>
/// <returns>array of dedented lines</returns>
/// <code doctest="true">
/// Assert.That(Dedent(""), Is.EquivalentTo(new[] {""}));
/// Assert.That(Dedent("test me"), Is.EquivalentTo(new[] {"test me"}));
/// Assert.That(Dedent("test\nme"), Is.EquivalentTo(new[] {"test", "me"}));
/// Assert.That(Dedent("test\n  me"), Is.EquivalentTo(new[] {"test", "  me"}));
/// Assert.That(Dedent("test\n  me\n    again"), Is.EquivalentTo(new[] {"test", "me", "  again"}));
/// Assert.That(Dedent("  test\n  me\n    again"), Is.EquivalentTo(new[] {"  test", "me", "  again"}));
/// </code>
private static string[] Dedent(string text)
{
    var lines = text.Split(
        new[] {"\r\n", "\r", "\n"},
        StringSplitOptions.None);

    // Search for the first non-empty line starting from the second line.
    // The first line is not expected to be indented.
    var firstNonemptyLine = -1;
    for (var i = 1; i < lines.Length; i++)
    {
        if (lines[i].Length == 0) continue;

        firstNonemptyLine = i;
        break;
    }

    if (firstNonemptyLine < 0) return lines;

    // Search for the second non-empty line.
    // If there is no second non-empty line, we can return immediately as we
    // can not pin the indent.
    var secondNonemptyLine = -1;
    for (var i = firstNonemptyLine + 1; i < lines.Length; i++)
    {
        if (lines[i].Length == 0) continue;

        secondNonemptyLine = i;
        break;
    }

    if (secondNonemptyLine < 0) return lines;

    // Match the common prefix with at least two non-empty lines
    
    var firstNonemptyLineLength = lines[firstNonemptyLine].Length;
    var prefixLength = 0;
    
    for (int column = 0; column < firstNonemptyLineLength; column++)
    {
        char c = lines[firstNonemptyLine][column];
        if (c != ' ' && c != '\t') break;
        
        bool matched = true;
        for (int lineIdx = firstNonemptyLine + 1; lineIdx < lines.Length; 
                lineIdx++)
        {
            if (lines[lineIdx].Length == 0) continue;
            
            if (lines[lineIdx].Length < column + 1)
            {
                matched = false;
                break;
            }

            if (lines[lineIdx][column] != c)
            {
                matched = false;
                break;
            }
        }

        if (!matched) break;
        
        prefixLength++;
    }

    if (prefixLength == 0) return lines;
    
    for (var i = 1; i < lines.Length; i++)
    {
        if (lines[i].Length > 0) lines[i] = lines[i].Substring(prefixLength);
    }

    return lines;
}

谢谢你。我想要的是textrwap.dedent行为,它只会剥离第一行缩进的空格数量,而不是盲目地剥离每行开头的所有空格。而你的实现恰好做到了这一点! - kjellander
请您能否再仔细检查一下并给我几个例子?它应该在第二行和接下来的行中寻找边距。我会尝试今天添加doctests。 - marko.ristin
@kjellander 我已添加了doctest,请告诉我如果您有具体的输入无法产生预期结果。 - marko.ristin

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