Интерполяция строк в C# 6 без использования $

165
28 января 2020, 14:30

В конфигурационном JSON файле используются строки следующего вида:

{
  "logFilePattern" : "{yyyy}.{mm}.{dd}.log"
}

Для замены используется множество разных текстовых заполнителей: вычисляемые пути файловой системы, которые зависят от пользователя и т.д.. Предполагается их замена и для этого в предыдущих версиях C# использовалось составное форматирование (могу ошибаться в термине) таким образом:

string name = "Fred";
Console.WriteLine(String.Format("Name = {0}", name));
// output: "Name = Fred"

С выходом C# 6 версии появилась возможность использовать именованные аргументы (могу ошибаться в термине) таким образом:

string name = "Fred";
Console.WriteLine($"Name = {name}");
// output: "Name = Fred"

Вопрос: как интерполировать строку с именованными аргументами сохраненую в переменной?

string stringPattern = "Name = {name}";
string name = "Fred";
// ?

Нашел здесь что это не возможно, т.к. код:

string name = "Fred";
string name2 = $"Name = {name}";

, компилятором приводится к:

string name = "fred";
string name2 = String.Format("Name = {0}", name);

Другим словами, это просто синтаксический сахар.

Answer 1

UPD

Если весь вопрос в том, как сгенерировать имя лог файла, то может проще обойтись таким способом? В конфигурации задаем валидный с т.з. .NET формат для даты:

{
  "logFilePattern" : "yyyy.MM.dd.log"
}

Затем в коде выкусываем его и получаем имя файла:

const string extension = ".log";
var datePattern = logFilePattern.Remove(logFilePattern.IndexOf(extension), extension.Length);
DateTime date = DateTime.UtcNow;
var logFileName = date.ToString(datePattern) + extension;

О задаче в общем

Наивная реализация может состоять просто в замене значения:

string stringPattern = "Name = {name}";
string name = "Fred";
string formatted = stringPattern.Replace($"{{nameof(name)}}", name);

Тут есть два недостатка:

  • код неуниверсальный (хотелось бы иметь отдельный метод, типа FormatString(pattern, value1, value2, ...))
  • не учитывается возможное эскапирование фигурных скобок (строка "{{name}}" не является шаблоном, однако замена будет произведена)
  • при наличии нескольких значений мы будем плодить лишние строки

Метод вида FormatString(pattern, value1, value2, ...), насколько я понимаю, нереален, поскольку нет способа в вызываемом методе узнать имена параметров, с которыми он вызывался. Поэтому можно остановиться на, например, словаре:

public static string FormatString(string pattern, IDictionary<string, object> values)

Эскапирование можно обработать регулярным выражением, но учитывая, что параметров может быть несколько, это тоже может быть излишне тяжелой операцией. Поэтому реализация в общем-то зависит от желаемой степени оптимизированности, в идеале надо вручную проходить char[]/List<char> и менять его на ходу.

На ночь глядя получилось так:

public static string FormatString(string pattern, IDictionary<string, object> values)
{
    var buffer = pattern.ToList();
    int replaceStartIndex = -1;
    int replaceEndIndex = -1;
    int index = 0;
    while (index < buffer.Count)
    {
        var character = buffer[index];
        if (character == '{')
        {
            if (replaceStartIndex == -1)
            {
                replaceStartIndex = index;
            }
            else
            {
                replaceStartIndex = -1;
            }
        }
        else if (character == '}')
        {
            if (replaceEndIndex == -1)
            {
                replaceEndIndex = index;
            }
            else
            {
                replaceEndIndex = -1;
            }
        }
        if (replaceStartIndex > -1 && replaceEndIndex > -1)
        {
            var key = new string(buffer.Skip(replaceStartIndex + 1).Take(replaceEndIndex - replaceStartIndex - 1).ToArray());
            var value = values[key];
            var stringValue = value?.ToString();
            buffer.RemoveRange(replaceStartIndex, replaceEndIndex - replaceStartIndex + 1);
            if (stringValue != null)
            {
                buffer.InsertRange(replaceStartIndex, stringValue.ToCharArray());
                index = replaceStartIndex + stringValue.Length;
            }
            else
            {
                index = replaceStartIndex;
            }
            replaceStartIndex = -1;
            replaceEndIndex = -1;
        }
        else
        {
            index++;
        }
    }
    return string.Join("", buffer);
}

Проверка:

static void Main(string[] args)
{
    string stringPattern = "Name = {name}, Age = {age}, Null = {nullString}, Escape = {{escape}}";
    string name = "Fred";
    int age = 42;
    string nullString = null;
    string escape = "You shouldn't see this";
    var values = new Dictionary<string, object>()
    {
        [nameof(name)] = name,
        [nameof(age)] = age,
        [nameof(nullString)] = nullString,
        [nameof(escape)] = escape
    };
    string formatted = FormatString(stringPattern, values);
    Console.WriteLine(formatted);
}

Вывод:

Name = Fred, Age = 42, Null = , Escape = {{escape}}

Answer 2

Вот тут сказано, что приведенная в топике строка является типом System.FormattableString. По поводу которого читаем что можно получить по индексу нужный инжектированный параметр. И просто можем сформировать новую строку со своим инжектом. Это возможно в рамках передаваемой интерполированной строки в функцию, например.. Возможно таким способом получить нужный результат?!

READ ALSO
Как вывести окно поверх всех окон в Windows?

Как вывести окно поверх всех окон в Windows?

Нужно вывести окно поверх всех окон, для эффекта водяного знакаВозможно ли это сделать с помощью WPF без использования WinApi?

240
Xamarin forms for desktop

Xamarin forms for desktop

Нужно разработать мультиплатформенное десктопное(в будущем возможно и мобила) приложениеВыбрал Xamarin Forms, он только для мобильных приложений?...

202
Сортировка списка из внешнего файла С# [закрыт]

Сортировка списка из внешнего файла С# [закрыт]

Хотите улучшить этот вопрос? Добавьте больше подробностей и уточните проблему, отредактировав это сообщение

191
Как построить график

Как построить график

Подскажите пожалуйста, как построить график следующего типа

172