C#6中使用命名参数的字符串插值

19

假设我在数据库中存储了一个元数据资源字符串,它将返回以下内容:

var pageTitle = "Shop the latest {category1} Designer {category2} {category3} at www.abc.com";

我想用变量值替换{占位符};

var category1 = "womenswear";
var category2 = "dresses";
var category3 = "cocktail dresses";

我已经尝试过,但没有成功;

var newTitle = $"pageTitle, category1, category2, category3";
var newTitle = $(pageTitle, category1, category2, category3);

我知道可以使用string.Replace(),但这会带来性能开销。有没有人知道如何使用最新的C#字符串插值来高效地完成这个操作?

5个回答

20

在这里你不能使用字符串插值。字符串插值是针对string.Format的编译时重写方法,你应该使用这个解决方案:

var newTitle = string.Format("{0}, {1}, {2}, {3}", pageTitle, category1, category2, category3);

或者使用string.Join

var newTitle - string.Join(", ", new string[] { pageTitle, category1, category2, category3 });

因为你从数据库加载,所以在你的情况下最好继续使用 string.Replace


@DStanley 我只是在上一个示例的基础上继续前进。在 OP 的情况下,替换是实际解决方案。 - Patrick Hofman
有没有一种扩展方法,它在内部使用 string.Format,但是却使用实际变量名作为占位符,而不是 {0}{1} 等等?这是否可能? - elolos
1
可能吧,但就我所知,扩展方法怎么知道调用方法中的变量是什么? - D Stanley
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Zack

18

我知道可以使用string.Replace(),但这会有一定的性能开销。

仅仅是纳秒级别的。你不应该过早优化。

正如所说,C# 6字符串插值特性是在编译时实现的。如果你想在运行时实现这个功能,那么就需要使用字符串替换。

我猜测你的字符串格式是为了方便编辑而不是固定的替换顺序,所以只需这样做:

var input = "Shop the latest {category1} Designer {category2} {category3} at www.abc.com";
var category1 = "womenswear";
var category2 = "dresses";
var category3 = "cocktail dresses";

var result = input.Replace("{category1}", category1)
                  .Replace("{category2}", category2)
                  .Replace("{category3}", category3);

这将直接起作用。您还可以将替换值存储在字典中,并循环遍历键以将键替换为其对应的值。


5
吹毛求疵:如果 category1 == "{category2}",那么这将产生意料之外的结果 :) 但我承认:我一直在使用它。 - usr
1
@usr 绝对没错,遍历字符串并将每个 {replacement} 与字典匹配是更好的方法,可以参考dasblinkenlight的回答。 - CodeCaster
input.Replace("{" + nameof(category1) + "}", category1) 也可以工作,对吧?但这种方式对我来说有点太过于“魔法字符串”了。 - Zack
@Zack 是的,但这仍然容易受到复制粘贴错误的影响。使用包含键和替换值的字典是更好的选择,而不是使用局部变量。 - CodeCaster
@usr 确实,这只是为了指出一种没有多次替换问题的机制。如果您想要更好的正则表达式,您可以随时想出其他东西,比如 \{(\w+)\} 或者 \{([^{}\s]+)\},甚至是 \{((?>(?<b>\{)|(?<-b>\})|(?>[^{}\s]+))+(?(b)(?!)))\} 如果你喜欢的话 ;) - Lucas Trzesniewski
显示剩余2条评论

11
尽管 .NET 没有内置的能力来实现此功能,但您可以使用正则表达式轻松构建一个相当不错的实现:
public static string Format(string formatWithNames, IDictionary<string,object> data) {
    int pos = 0;
    var args = new List<object>();
    var fmt = Regex.Replace(
        formatWithNames
    ,   @"(?<={)[^}]+(?=})"
    ,   new MatchEvaluator(m => {
            var res = (pos++).ToString();
            var tok = m.Groups[0].Value.Split(':');
            args.Add(data[tok[0]]);
            return tok.Length == 2 ? res+":"+tok[1] : res;
        })
    );
    return string.Format(fmt, args.ToArray());
}

这个想法是用连续的数字替换名称(参见sb.Append(pos++)),构建一个对象参数数组(参见args.Add(data[tok[0]])),并将结果传递给string.Format以使用string的格式化功能。
现在,您可以将资源和键值对字典一起传递给此Format,并使用.NET的内置格式化功能获取格式化字符串:
var x = new Dictionary<string,object> {
    {"brown", 1}
,   {"jumps", 21}
,   {"lazy", 42}
};
Console.WriteLine("{0}", Format("Quick {brown} fox {jumps:C} over the {lazy:P} dog", x));

请注意字符串文字前面没有$,因此这里没有使用C#的内置插值。

这将打印:

Quick 1 fox $21.00 over the 4,200.00 % dog

该方法基于Scott Hanselman提出的想法, 但格式不同。 演示。

你想这样做而不是使用带有MatchEvaluator的Regex.Replace,有什么原因吗? - Luaan
@Luaan 我想保留Scott的代码,但MatchEvaluator更加简洁。请看一下修改后的内容。 - Sergey Kalinichenko

5
如果您不想使用字符串替换,那么您需要使用原始格式语法:
var pageTitle = "Shop the latest {0} Designer {1} {2} at www.abc.com";

var category1 = "womenswear";
var category2 = "dresses";
var category3 = "cocktail dresses";

var newTitle = string.Format(pageTitle, category1, category2, category3);

或者将格式化字符串放在变量之后
var category1 = "womenswear";
var category2 = "dresses";
var category3 = "cocktail dresses";

var newTitle = $"Shop the latest {0} Designer {0} {0} at www.abc.com";

字符串插值在编译时会被转换为一个 string.Format 调用,因此不可能在运行时将格式化字符串作为变量。

我认为你的第一个例子可能是OP必须这样做的方式,如果他们从类似于数据库的存储获取格式化字符串。 - Zack

1
请查看我的库,解决这个问题TinyTools,如果您使用它,请给我点赞,如果您喜欢,请在Github上留下一颗星。
    var person = new Person
    {
        FirstName = "John",
        LastName = "Smith"
    };
    var template = "Hello world, I'm {FirstName} {LastName}";

    var result = template.Interpolate(person);
    result.ShouldBe("Hello world, I'm John Smith");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接