Строка интерполяции строк С# 6.0 - программирование
Подтвердить что ты не робот

Строка интерполяции строк С# 6.0

В С# 6.0 есть интерполяция строк - хорошая возможность для форматирования строк, таких как:

 var name = "John";
 WriteLine($"My name is {name}");

Пример конвертируется в

 var name = "John";
 WriteLine(String.Format("My name is {0}", name));

С точки зрения локализации, гораздо лучше хранить строки вроде:

"My name is {name} {middlename} {surname}" 

чем в нотации String.Format:

"My name is {0} {1} {2}"

Как использовать интерполяцию строк для локализации .NET? Будет ли способ поместить $ "..." в файлы ресурсов? Или строки должны храниться как "... {name}" и как-то интерполироваться на лету?

PS Этот вопрос НЕ о том, "как сделать расширение string.FormatIt" (таких программ очень много, ТАК ответы и т.д.). Этот вопрос о чем-то вроде расширения Roslyn для "интерполяции строк" в контексте "локализации" (оба являются терминами в словаре MS.NET) или динамического использования, как предложил Дилан.

4b9b3361

Ответ 1

С помощью пакета Microsoft.CodeAnalysis.CSharp.Scripting вы можете добиться этого.

Вам нужно будет создать объект для хранения данных, ниже используется динамический объект. Вы также можете создать определенный класс со всеми необходимыми свойствами. Причина обертывания динамического объекта в классе описана здесь.

public class DynamicData
{
    public dynamic Data { get; } = new ExpandoObject();
}

Затем вы можете использовать его, как показано ниже.

var options = ScriptOptions.Default
    .AddReferences(
        typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).GetTypeInfo().Assembly,
        typeof(System.Runtime.CompilerServices.DynamicAttribute).GetTypeInfo().Assembly);

var globals = new DynamicData();
globals.Data.Name = "John";
globals.Data.MiddleName = "James";
globals.Data.Surname = "Jamison";

var text = "My name is {Data.Name} {Data.MiddleName} {Data.Surname}";
var result = await CSharpScript.EvaluateAsync<string>($"$\"{text}\"", options, globals);

Это компилирует фрагмент кода и выполняет его, так что это истинная интерполяция строки С#. Хотя вам придется учитывать производительность этого процесса, так как он на самом деле компилирует и выполняет ваш код во время выполнения. Чтобы обойти эту производительность, попробуйте использовать CSharpScript.Create для компиляции и кэширования кода.

Ответ 2

Интерполированная строка оценивает блок между фигурными фигурными скобками как выражение С# (например, {expression}, {1 + 1}, {person.FirstName}).

Это означает, что выражения в интерполированной строке должны ссылаться на имена в текущем контексте.

Например, этот оператор не будет компилироваться:

var nameFormat = $"My name is {name}"; // Cannot use *name*
                                       // before it is declared
var name = "Fred";
WriteLine(nameFormat);

Аналогично:

class Program
{
    const string interpolated = $"{firstName}"; // Name *firstName* does not exist
                                                // in the current context
    static void Main(string[] args)
    {
        var firstName = "fred";
        Console.WriteLine(interpolated);
        Console.ReadKey();
    }
}

Чтобы ответить на ваш вопрос:

Механизм, предоставляемый каркасом, не существует, чтобы оценить интерполированные строки во время выполнения. Поэтому вы не можете хранить строки и интерполировать "на лету" из коробки.

Существуют библиотеки, которые обрабатывают интерполяцию строк во время выполнения.

Ответ 3

В соответствии с это обсуждение на сайте Roslyn codeplex, интерполяция строк, скорее всего, не будет совместима с файлами ресурсов (основное внимание):

Интерполяция строк может быть более быстрой и простой для отладки, чем String.Format или конкатенация...

Dim y = $"Robot {name} reporting
{coolant.name} levels are {coolant.level}
{reactor.name} levels are {reactor.level}"

Однако этот пример является подозрительным. Большинство профессиональных программистов не будут писать строки, обращенные к пользователю, в коде. Вместо этого они будут хранить эти строки в ресурсах (.resw,.resx или .xlf) по причинам локализации. Таким образом, здесь нет никакой пользы для строковой интерполяции.

Ответ 4

Как уже говорилось в предыдущих ответах: вы не можете загружать строку формата во время выполнения (например, из файлов ресурсов) для интерполяции строк, потому что она используется во время компиляции.

Если вы не заботитесь о функции времени компиляции и хотите иметь имена с именами, вы можете использовать что-то вроде этого метода расширения:

public static string StringFormat(this string input, Dictionary<string, object> elements)
{
    int i = 0;
    var values = new object[elements.Count];
    foreach (var elem in elements)
    {
        input = Regex.Replace(input, "{" + Regex.Escape(elem.Key) + "(?<format>[^}]+)?}", "{" + i + "${format}}");
        values[i++] = elem.Value;
    }
    return string.Format(input, values);
}

Имейте в виду, что вы не можете иметь встроенные выражения типа {i+1} здесь и что это не код с лучшей производительностью.

Вы можете использовать это со словарем, который вы загружаете из файлов ресурсов или в виде строки следующим образом:

var txt = "Hello {name} on {day:yyyy-MM-dd}!".StringFormat(new Dictionary<string, object>
            {
                ["name"] = "Joe",
                ["day"] = DateTime.Now,
            });

Ответ 5

Интерполяция строк С# 6.0 не поможет вам, если строка формата не находится в исходном коде С#. В этом случае вам придется использовать другое решение, например эту библиотеку.

Ответ 6

Предполагая, что ваш вопрос больше о том, как локализовать интерполированные строки в исходном коде, а не как обрабатывать интерполированные ресурсы строк...

С учетом кода примера:

var name = "John";
var middlename = "W";
var surname = "Bloggs";
var text = $"My name is {name} {middlename} {surname}";
Console.WriteLine(text);

Вывод, очевидно, равен:

My name is John W Bloggs

Теперь измените назначение текста, чтобы вместо этого выбрать перевод:

var text = Translate($"My name is {name} {middlename} {surname}");

Translate выполняется следующим образом:

public static string Translate(FormattableString text)
{
    return string.Format(GetTranslation(text.Format),
        text.GetArguments());
}

private static string GetTranslation(string text)
{
    return text; // actually use gettext or whatever
}

Вам нужно предоставить свою собственную реализацию GetTranslation; он получит строку типа "My name is {0} {1} {2}" и должен использовать GetText или ресурсы или подобные, чтобы найти и вернуть подходящий перевод для этого, или просто вернуть исходный параметр для пропуска перевода.

Вам все равно придется документировать для ваших переводчиков, что означают номера параметров; текст, используемый в исходной кодовой строке, не существует во время выполнения.

Если, например, в этом случае GetTranslation возвращается "{2}. {0} {2}, {1}. Don't wear it out." (эй, локализация - это не только язык!), тогда выход полной программы будет:

Bloggs.  John Bloggs, W.  Don't wear it out.

Сказав это, в то время как использование этого стиля перевода легко развивается, его трудно перевести, поскольку строки закопаны в коде и только поверхность во время выполнения. Если у вас нет инструмента, который может статически исследовать ваш код и извлекать все переводимые строки (без необходимости использовать этот путь кода во время выполнения), вам лучше использовать более традиционные файлы resx, поскольку они по сути дают вам таблицу с текстом для быть переведено.

Ответ 7

Строковую интерполяцию сложно совместить с локализацией, потому что компилятор предпочитает переводить ее в string.Format(...), которая не поддерживает локализацию. Однако есть хитрость, которая позволяет сочетать локализацию и интерполяцию строк; это описано в конце этой статьи.

Обычно строковая интерполяция переводится в string.Format, поведение которого нельзя изменить. Однако почти так же, как лямбда-методы иногда становятся деревьями выражений, компилятор переключится с string.Format на FormattableStringFactory.Create (метод .NET 4.6), если целевой метод принимает объект System.FormattableString.

Проблема в том, что компилятор предпочитает вызывать string.Format если это возможно, поэтому, если бы была перегрузка Localized() которая приняла FormattableString, он не работал бы с интерполяцией строки, потому что компилятор С# просто проигнорировал бы это [потому что есть перегрузка который принимает простую строку]. На самом деле, это еще хуже: компилятор также отказывается использовать FormattableString при вызове метода расширения.

Это может работать, если вы используете метод без расширения. Например:

static class Loca
{
    public static string lize(this FormattableString message)
        { return message.Format.Localized(message.GetArguments()); }
}

Тогда вы можете использовать это так:

public class Program
{
    public static void Main(string[] args)
    {
        Localize.UseResourceManager(Resources.ResourceManager);

        var name = "Dave";
        Console.WriteLine(Loca.lize($"Hello, {name}"));
    }
}

Важно понимать, что компилятор преобразует строку $"..." в устаревшую строку формата. Таким образом, в этом примере Loca.lize фактически получает "Hello, {0}" в качестве строки формата, а не "Hello, {name}".

Ответ 8

Если мы используем интерполяцию, то мы думаем с точки зрения методов, а не констант. В этом случае мы могли бы определить наши переводы как методы:

public abstract class InterpolatedText
{
    public abstract string GreetingWithName(string firstName, string lastName);
}

public class InterpolatedTextEnglish : InterpolatedText
{
    public override string GreetingWithName(string firstName, string lastName) =>
        $"Hello, my name is {firstName} {lastName}.";
}

Затем мы можем загрузить реализацию InterpolatedText для конкретной культуры. Это также дает возможность реализовать запасной вариант, поскольку одна реализация может наследоваться от другой. Если английский является языком по умолчанию и другие реализации наследуют его, по крайней мере, будет что-то отображаться, пока не будет предоставлен перевод.

Это кажется немного неортодоксальным, но предлагает некоторые преимущества:

Прежде всего, строка, используемая для интерполяции, всегда хранится в строго типизированном методе с четко определенными аргументами.

Учитывая это: "Hello, my name is {0} {1}" можем ли мы определить, что заполнители представляют имя и фамилию в этом порядке? Всегда будет метод, который сопоставляет значения с местозаполнителями, но будет меньше места для путаницы, когда интерполированная строка хранится со своими аргументами.

Точно так же, если мы храним наши строки перевода в одном месте и используем их в другом, становится возможным изменить их таким образом, чтобы это нарушало код, использующий их. Мы можем добавить {2} к строке, которая будет использоваться в другом месте, и этот код завершится с ошибкой во время выполнения.

Используя строковую интерполяцию это невозможно. Если наша строка перевода не соответствует доступным аргументам, она даже не скомпилируется.


Есть недостатки, хотя я вижу трудности в поддержании любого решения.

Самым большим является мобильность. Если ваш перевод написан на С# и вы переключаетесь, экспортировать все ваши переводы не так просто.

Это также означает, что если вы хотите отправлять переводы разным людям (если у вас нет одного человека, который говорит все), то переводчики должны изменить код. Это простой код, но, тем не менее, код.

Ответ 9

Интерполированные строки не могут быть реорганизованы из их (переменной) области из-за использования встроенных переменных в них.

Единственный способ переместить строковую литералную часть - передать переменные границы области в качестве параметра в другое место и пометить их позицию в строке специальными заполнителями. Однако это решение уже "придумано" и там:

string.Format("literal with placeholers", parameters);

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

Затем вы можете реорганизовать "literal with placeholers" ресурс.