Многострочное форматирование для дословных строк в 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;
}