c# - 如何在运行时使用 Interpolated [Named] 值获得类似 String.Format 的行为?

标签 c# string formatting

我有一类允许的替换值:

class MaskDictionary
{
    public int id { get; set; }
    public string last { get; set; }
    public string lastinitial { get; set; }
    public string first { get; set; }
    public string firstinitial { get; set; }
    public string salutation { get; set; }
    public DateTime today { get; set; }
}

我想将格式化字符串作为用户输入,例如:

string userFormat = "{last}, {first} {today}";

并生成插值。概念上类似于:

string.Format("{last}, {first} {today}", MaskDictionary);

但是使输入字符串动态化失败了:

string.Format(userFormat, MaskDictionary);

提供运行时格式化的简单、干净的方法是什么?

有一些笨拙的选项使用反射和递归替换,比如

        string userFormat = "{last}, {first} {today}";
        PropertyInfo[] properties = typeof(MaskDictionary).GetProperties();
        foreach (PropertyInfo property in properties)
        {
            userFormat = string.Replace(property.name, property.GetValue(mask));
        }

但必须有更好的方法。

--更新答案比较--

我测试了答案中提出的两个解决方案的性能,并得到了非常令人惊讶的结果。

  static class Format2
    {
        static public string Format(string format, MaskDictionary md)
        {
            string val = format;
            foreach (PropertyInfo property in typeof(MaskDictionary).GetProperties())
            {
                val = val.Replace("{" + property.Name + "}", property.GetValue(md).ToString());
            }
            return val;
        }
    }
static class Format1
{
    public static string FormatWith(this string format, IFormatProvider provider, object source)
    {
        if (format == null)
            throw new ArgumentNullException("format");

        Regex r = new Regex(@"(?<start>\{)+(?<property>[\w\.\[\]]+)(?<format>:[^}]+)?(?<end>\})+",
          RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

        List<object> values = new List<object>();
        string rewrittenFormat = r.Replace(format, delegate (Match m)
        {
            Group startGroup = m.Groups["start"];
            Group propertyGroup = m.Groups["property"];
            Group formatGroup = m.Groups["format"];
            Group endGroup = m.Groups["end"];

            values.Add((propertyGroup.Value == "0")
              ? source
              : DataBinder.Eval(source, propertyGroup.Value));

            return new string('{', startGroup.Captures.Count) + (values.Count - 1) + formatGroup.Value
              + new string('}', endGroup.Captures.Count);
        });

        return string.Format(provider, rewrittenFormat, values.ToArray());
    }
}

Regex 解决方案比较慢,非常慢。使用具有 5 个属性字典对象和 105 个属性字典对象的短格式字符串(20 个字符,3 个替换)以及一个长格式字符串(2000 个字符,3 个替换)和一个长字典对象的 1000 次迭代,我得到以下结果:

短格式,小词典
正则表达式 - 2150 毫秒
替换 - 3 毫秒
格式短,字典大
正则表达式 - 2160 毫秒
替换 - 30 毫秒
长格式,短字典
正则表达式 - 2170 毫秒
替换 - 26 毫秒
长格式,大词典
正则表达式 - 2250 毫秒
替换 - 330 毫秒

Replace 不能很好地扩展到大字典,但它的启动速度要快得多,以至于需要大字典加上非常长的格式字符串才能变慢。使用 105 属性字典,大约需要 16,000 个字符格式字符串来处理相同数量的时间,~2500 毫秒。使用 5 属性小字典,正则表达式从未如此快。一个 600K 字符的格式字符串用于正则表达式需要 14000 毫秒,用于替换需要 7000 毫秒,而一个 1.7M 字符格式字符串需要 38000 毫秒与 21000 毫秒。只要字典对象的大小合理且格式字符串短于 80 页,替换就会获胜。

最佳答案

James Newton King(JSON 专家)使用定义的 FormatWith() 扩展方法 in this blog post这将基本上完成你正在尝试做的事情:

public static string FormatWith(this string format, IFormatProvider provider, object source)
{
  if (format == null)
    throw new ArgumentNullException("format");

  Regex r = new Regex(@"(?<start>\{)+(?<property>[\w\.\[\]]+)(?<format>:[^}]+)?(?<end>\})+",
    RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

  List<object> values = new List<object>();
  string rewrittenFormat = r.Replace(format, delegate(Match m)
  {
    Group startGroup = m.Groups["start"];
    Group propertyGroup = m.Groups["property"];
    Group formatGroup = m.Groups["format"];
    Group endGroup = m.Groups["end"];

    values.Add((propertyGroup.Value == "0")
      ? source
      : DataBinder.Eval(source, propertyGroup.Value));

    return new string('{', startGroup.Captures.Count) + (values.Count - 1) + formatGroup.Value
      + new string('}', endGroup.Captures.Count);
  });

  return string.Format(provider, rewrittenFormat, values.ToArray());
}

它基本上依赖正则表达式和 .NET Databinder处理执行实际匹配和替换的类。

关于c# - 如何在运行时使用 Interpolated [Named] 值获得类似 String.Format 的行为?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36437427/

相关文章:

java - 字符串的第一个和最后一个字符

sass - 有没有办法将 ReSharper 代码清理设置应用于 .scss (SASS) 文件?

Python:使用变量格式化多行字符串

javascript - 我怎样才能让这个 javascript 调用我的 C# 方法并传递参数

c# - ServiceHost仅支持类服务类型

java - 需要了解的String、String常量池和String intern方法

java - 如何使用可变参数从函数返回一个字符串

python - 解析文本日志文件以从日志消息中提取某些数据字段

c# - 如何使 Fluent API 配置与 MVC 客户端验证一起使用?

c# - 在 C# 中打印数组/列表到 Excel