代码之家  ›  专栏  ›  技术社区  ›  Espo

如何创建更友好的Strug.Frand语法?

  •  44
  • Espo  · 技术社区  · 15 年前

    我需要在程序中创建一个很长的字符串,并且一直在使用string.format。我面临的问题是当你有超过8-10个参数时,要跟踪所有的数字。

    是否可以创建某种形式的重载来接受类似的语法?

    String.Format("You are {age} years old and your last name is {name} ",
    {age = "18", name = "Foo"});
    
    6 回复  |  直到 7 年前
        1
  •  70
  •   Marc Gravell    15 年前

    对于匿名类型(下面的示例)或常规类型(域实体等),可以使用以下方法吗?

    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
  •  2
  •   Preet Sangha    15 年前

    不完全一样但有点欺骗…使用扩展方法、字典和少量代码:

    像这样的…

      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
  •  2
  •   Jordan Wallwork    9 年前

    从C 6开始,这种字符串插值现在可以使用新的 string interpolation 语法:

    var formatted = $"You are {age} years old and your last name is {name}";
    
        4
  •  1
  •   RvdK    15 年前

    如果age/name是应用程序中的变量呢?所以你需要一个排序语法来使它几乎是唯一的,比如{age}?

    如果8-10个参数有问题:为什么不使用

    "You are " + age + " years old and your last name is " + name + "
    
        5
  •  1
  •   Stefan Steinegger    15 年前

    基本实现:

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

    您也可以使用匿名类,但由于需要反射,因此速度要慢得多。

    对于真正的实现,应该使用正则表达式

    • 允许转义{}
    • 检查是否有未替换的占位符,这很可能是编程错误。
        6
  •  0
  •   Ryan    7 年前

    尽管C 6.0现在可以通过字符串插值来实现这一点,但有时需要在运行时通过动态格式字符串来实现。我无法使用其他需要databinder.eval的方法,因为它们在.NET Core中不可用,并且对regex解决方案的性能不满意。

    考虑到这一点,这里有一个无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's 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 或钥匙 伸缩缓冲器 . 当参数关闭时,键的值 伸缩缓冲器 用于在字典中查找参数值,然后将其推入输出 伸缩缓冲器 .

    编辑:

    我已经把这个变成了 https://github.com/crozone/FormatWith