String interpolation in C# 6 without using $

The following lines are used in the configuration JSON file:

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

Many different text placeholders are used for replacement: calculated file system paths that depend on the user, etc. They are supposed to be replaced, and for this purpose in previous versions of C#, composite formatting was used (I may be mistaken in the term) in this way:

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

With the release of the C# 6 version, it is now possible to use named names. arguments (I may be mistaken in the term) in this way:

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

Question: how to interpolate a string with named arguments stored in a variable?

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


I found here that this is not possible, because the code:
string name = "Fred";
string name2 = $"Name = {name}";

, the compiler is reduced to:

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

In other words, it's just syntactic sugar.

Author: Дух сообщества, 2016-09-07

2 answers

UPD

If the whole question is how to generate the name of the log file, then it may be easier to do this way? In the configuration, we set a valid .NET format for the date. :

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

Then, in the code, we bite it out and get the file name:

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

About the problem in general

A naive implementation may simply consist of replacing the value:

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

There are two drawbacks here:

  • non-universal code (I would like to have a separate method, such as FormatString(pattern, value1, value2, ...))
  • does not take into account possible escaping of curly brackets (the string "{{name}}" is not a template, but the replacement will be made)
  • if there are several values, we will produce extra rows

A method of the form FormatString(pattern, value1, value2, ...), as far as I understand, is unrealistic, since there is no way in the called method to find out the names of the parameters with which it was called. Therefore, you can stop at, for example, the dictionary:

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

Escaping is possible process with a regular expression, but given that there may be several parameters, this may also be an unnecessarily heavy operation. Therefore, the implementation in general depends on the desired degree of optimization, ideally you need to manually pass char[]/List<char> and change it on the go.

At night looking it turned out like this:

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);
}

Check:

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);
}

Conclusion:

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

 3
Author: andreycha, 2016-09-07 21:54:23

Here here it is said that the string given in the topic is of type System.FormattableString. About which we read that you can get the desired injected parameter from the index. And we can just form a new line with our own injection. This is possible within the framework of the interpolated string passed to the function, for example.. Is it possible to get the desired result in this way?!

 0
Author: KamiRonin, 2017-03-10 08:52:05