Многострочное форматирование для дословных строк в C# (префикс @)

Я люблю использовать @"strings" в C#, особенно когда у меня много многострочного текста. Единственное раздражение в том, что при этом мое форматирование кода идет на попятную, потому что вторая и большая строки сдвигаются полностью влево вместо использования отступа моего красиво отформатированного кода. Я знаю, что это сделано по замыслу, но есть ли какой-нибудь вариант / способ взлома, позволяющий этим строкам отступать без добавления фактических табуляций / пробелов в вывод?

добавление примера:

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

Объявление моей переменной имеет отступ на "правильную" глубину, но вторая и последующие строки в строке сдвигаются к левому краю, поэтому код выглядит довольно уродливо. Вы можете добавить вкладки в начало строк 2 и 3, но сама строка будет содержать эти вкладки... имеет смысл?

5 ответов

Как насчет расширения строки? Обновление: я перечитал ваш вопрос и надеюсь, что есть лучший ответ. Это то, что меня тоже беспокоит, и необходимость его решать, как показано ниже, разочаровывает, но, с другой стороны, это работает.

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

Cymen дал правильное решение. Я использую аналогичный подход, полученный из метода Scala's 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

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

Я не могу придумать ответ, который бы полностью удовлетворил ваш вопрос, однако вы могли бы написать функцию, которая удаляет начальные пробелы из строк текста, содержащихся в строке, и вызывала ее при каждом создании такой строки.

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

Да, это взлом, но в своем вопросе вы указали свое согласие на взлом.

Вот длинное решение, которое пытается имитировать textwrap.dedent насколько это возможно.

/// <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>
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;
}
Другие вопросы по тегам