Подтвердить что ты не робот

Как создать более удобный синтаксис string.format?

Мне нужно создать очень длинную строку в программе и использовать String.Format. Проблемой, с которой я столкнулся, является отслеживание всех чисел, когда у вас более 8-10 параметров.

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

String.Format("You are {age} years old and your last name is {name} ",
{age = "18", name = "Foo"});
4b9b3361

Ответ 1

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

static void Main()
{
    string s = Format("You are {age} years old and your last name is {name} ",
        new {age = 18, name = "Foo"});
}

с помощью:

static readonly Regex rePattern = new Regex(
    @"(\{+)([^\}]+)(\}+)", RegexOptions.Compiled);
static string Format(string pattern, object template)
{
    if (template == null) throw new ArgumentNullException();
    Type type = template.GetType();
    var cache = new Dictionary<string, string>();
    return rePattern.Replace(pattern, match =>
    {
        int lCount = match.Groups[1].Value.Length,
            rCount = match.Groups[3].Value.Length;
        if ((lCount % 2) != (rCount % 2)) throw new InvalidOperationException("Unbalanced braces");
        string lBrace = lCount == 1 ? "" : new string('{', lCount / 2),
            rBrace = rCount == 1 ? "" : new string('}', rCount / 2);

        string key = match.Groups[2].Value, value;
        if(lCount % 2 == 0) {
            value = key;
        } else {
            if (!cache.TryGetValue(key, out value))
            {
                var prop = type.GetProperty(key);
                if (prop == null)
                {
                    throw new ArgumentException("Not found: " + key, "pattern");
                }
                value = Convert.ToString(prop.GetValue(template, null));
                cache.Add(key, value);
            }
        }
        return lBrace + value + rBrace;
    });
}

Ответ 2

не совсем то же самое, но вроде spoofing его... используйте метод расширения, словарь и небольшой код:

что-то вроде этого...

  public static class Extensions {

        public static string FormatX(this string format, params KeyValuePair<string, object> []  values) {
            string res = format;
            foreach (KeyValuePair<string, object> kvp in values) {
                res = res.Replace(string.Format("{0}", kvp.Key), kvp.Value.ToString());
            }
            return res;
        }

    }

Ответ 3

примитивная реализация:

public static class StringUtility
{
  public static string Format(string pattern, IDictionary<string, object> args)
  {
    StringBuilder builder = new StringBuilder(pattern);
    foreach (var arg in args)
    {
      builder.Replace("{" + arg.Key + "}", arg.Value.ToString());
    }
    return builder.ToString();
  }
}

Использование:

StringUtility.Format("You are {age} years old and your last name is {name} ",
  new Dictionary<string, object>() {{"age" = 18, "name" = "Foo"}});

Вы также можете использовать анонимный класс, но это намного медленнее из-за отражения, которое вам нужно.

Для реальной реализации вы должны использовать регулярное выражение для

  • позволяют избежать {}
  • проверьте, есть ли заполнители, которые не заменяются, что, скорее всего, является ошибкой программирования.

Ответ 4

Как насчет того, будет ли age/name переменной в вашем приложении. Таким образом, вам нужен синтаксис вида, чтобы сделать его почти уникальным, например {age_1}?

Если у вас есть проблемы с 8-10 параметрами: почему бы не использовать

"You are " + age + " years old and your last name is " + name + "

Ответ 5

Начиная с С# 6, этот тип интерполяции строк теперь можно использовать с помощью нового синтаксиса string >

var formatted = $"You are {age} years old and your last name is {name}";

Ответ 6

Хотя С# 6.0 теперь может делать это со строчной интерполяцией, иногда это необходимо делать с динамическими строками формата во время выполнения. Я не смог использовать другие методы, которые требуют DataBinder.Eval из-за того, что они не доступны в .NET Core, и были недовольны работой решений Regex.

Имея это в виду, здесь ядрового анализатора, основанного на регулярном выражении, я написал. Он обрабатывает неограниченные уровни {{{escaping}}} и бросает FormatException, когда вход содержит несбалансированные фигурные скобки и/или другие ошибки. Хотя основной метод принимает Dictionary<string, object>, вспомогательный метод также может принимать object и использовать его параметры через отражение.

public static class StringExtension {
    /// <summary>
    /// Extension method that replaces keys in a string with the values of matching object properties.
    /// </summary>
    /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
    /// <param name="injectionObject">The object whose properties should be injected in the string</param>
    /// <returns>A version of the formatString string with keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, object injectionObject) {
        return formatString.FormatWith(GetPropertiesDictionary(injectionObject));
    }

    /// <summary>
    /// Extension method that replaces keys in a string with the values of matching dictionary entries.
    /// </summary>
    /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
    /// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
    /// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, IDictionary<string, object> dictionary) {
        char openBraceChar = '{';
        char closeBraceChar = '}';

        return FormatWith(formatString, dictionary, openBraceChar, closeBraceChar);
    }
        /// <summary>
        /// Extension method that replaces keys in a string with the values of matching dictionary entries.
        /// </summary>
        /// <param name="formatString">The format string, containing keys like {foo} and {foo:SomeFormat}.</param>
        /// <param name="dictionary">An <see cref="IDictionary"/> with keys and values to inject into the string</param>
        /// <returns>A version of the formatString string with dictionary keys replaced by (formatted) key values.</returns>
    public static string FormatWith(this string formatString, IDictionary<string, object> dictionary, char openBraceChar, char closeBraceChar) {
        string result = formatString;
        if (dictionary == null || formatString == null)
            return result;

        // start the state machine!

        // ballpark output string as two times the length of the input string for performance (avoids reallocating the buffer as often).
        StringBuilder outputString = new StringBuilder(formatString.Length * 2);
        StringBuilder currentKey = new StringBuilder();

        bool insideBraces = false;

        int index = 0;
        while (index < formatString.Length) {
            if (!insideBraces) {
                // currently not inside a pair of braces in the format string
                if (formatString[index] == openBraceChar) {
                    // check if the brace is escaped
                    if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
                        // add a brace to the output string
                        outputString.Append(openBraceChar);
                        // skip over braces
                        index += 2;
                        continue;
                    }
                    else {
                        // not an escaped brace, set state to inside brace
                        insideBraces = true;
                        index++;
                        continue;
                    }
                }
                else if (formatString[index] == closeBraceChar) {
                    // handle case where closing brace is encountered outside braces
                    if (index < formatString.Length - 1 && formatString[index + 1] == closeBraceChar) {
                        // this is an escaped closing brace, this is okay
                        // add a closing brace to the output string
                        outputString.Append(closeBraceChar);
                        // skip over braces
                        index += 2;
                        continue;
                    }
                    else {
                        // this is an unescaped closing brace outside of braces.
                        // throw a format exception
                        throw new FormatException($"Unmatched closing brace at position {index}");
                    }
                }
                else {
                    // the character has no special meaning, add it to the output string
                    outputString.Append(formatString[index]);
                    // move onto next character
                    index++;
                    continue;
                }
            }
            else {
                // currently inside a pair of braces in the format string
                // found an opening brace
                if (formatString[index] == openBraceChar) {
                    // check if the brace is escaped
                    if (index < formatString.Length - 1 && formatString[index + 1] == openBraceChar) {
                        // there are escaped braces within the key
                        // this is illegal, throw a format exception
                        throw new FormatException($"Illegal escaped opening braces within a parameter - index: {index}");
                    }
                    else {
                        // not an escaped brace, we have an unexpected opening brace within a pair of braces
                        throw new FormatException($"Unexpected opening brace inside a parameter - index: {index}");
                    }
                }
                else if (formatString[index] == closeBraceChar) {
                    // handle case where closing brace is encountered inside braces
                    // don't attempt to check for escaped braces here - always assume the first brace closes the braces
                    // since we cannot have escaped braces within parameters.

                    // set the state to be outside of any braces
                    insideBraces = false;

                    // jump over brace
                    index++;

                    // at this stage, a key is stored in current key that represents the text between the two braces
                    // do a lookup on this key
                    string key = currentKey.ToString();
                    // clear the stringbuilder for the key
                    currentKey.Clear();

                    object outObject;

                    if (!dictionary.TryGetValue(key, out outObject)) {
                        // the key was not found as a possible replacement, throw exception
                        throw new FormatException($"The parameter \"{key}\" was not present in the lookup dictionary");
                    }

                    // we now have the replacement value, add the value to the output string
                    outputString.Append(outObject);

                    // jump to next state
                    continue;
                } // if }
                else {
                    // character has no special meaning, add it to the current key
                    currentKey.Append(formatString[index]);
                    // move onto next character
                    index++;
                    continue;
                } // else
            } // if inside brace
        } // while

        // after the loop, if all braces were balanced, we should be outside all braces
        // if we're not, the input string was misformatted.
        if (insideBraces) {
            throw new FormatException("The format string ended before the parameter was closed.");
        }

        return outputString.ToString();
    }

    /// <summary>
    /// Creates a Dictionary from an objects properties, with the Key being the property's
    /// name and the Value being the properties value (of type object)
    /// </summary>
    /// <param name="properties">An object who properties will be used</param>
    /// <returns>A <see cref="Dictionary"/> of property values </returns>
    private static Dictionary<string, object> GetPropertiesDictionary(object properties) {
        Dictionary<string, object> values = null;
        if (properties != null) {
            values = new Dictionary<string, object>();
            PropertyDescriptorCollection props = TypeDescriptor.GetProperties(properties);
            foreach (PropertyDescriptor prop in props) {
                values.Add(prop.Name, prop.GetValue(properties));
            }
        }
        return values;
    }
}

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

EDIT:

Я превратил это в полный проект на https://github.com/crozone/FormatWith