.NET邮件客户端库的字符串模板库优化

在.NET邮件客户端库MailMergeLib的改进版本发布之际,寻找了一个快速的字符串模板库,以替代在邮件文本中替换占位符为变量值时使用的低性能正则表达式。仔细研究了几种解决方案,但它们都无法与Scott Rippey之前提出的SmartFormat.NET相媲美。这个库在NuGet上下载量达到了数万次,相当受欢迎。

选择字符串模板库的标准包括:开源,最好是MIT许可证;真正的解析,不使用正则表达式;与string.Format兼容;在C#代码中的小占用空间,不依赖外部程序集;可扩展性;性能接近string.Format;可以移植到.NET Core;易于集成到邮件客户端库中。这些标准也导致了Razor Engine的拒绝。

Scott和他的项目贡献者在一段时间内有不同的优先事项,因此该项目在一段时间内处于休眠状态。所以决定为项目做出贡献,处理用户报告的问题,将其移植到.NET Core(除了.NET Framework 4.0和4.5),并在觉得有缺陷的地方进行扩展。

关于C# 6中引入的字符串插值?字符串插值很有帮助: var addrList = new[] { new { Name = "Jim", Address = new { City = "New York", State = "NY" } } }; var result = string.Format($"{addrList[0].Name} in {addrList[0].Address.City}"); 字符串插值仅在编译时可用。因此,正在寻找一个在运行时可用的类似解决方案。这正是SmartFormat的目标。

关于Roslyn?是的,可以使用CSharpScript编译一个插值字符串,如下所示: namespace Roslyn { public class Program { public class Sample { public readonly int s = 12345; } static void Main(string[] args) { var sw = new Stopwatch(); sw.Start(); var state = CSharpScript.RunAsync(@" return string.Format($""The number is{s}.""); ", globals: new Sample()).Result; sw.Stop(); Console.WriteLine(sw.ElapsedMilliseconds); } } } 该示例已经包含了一个计时器,这将非常清楚地表明,这不是一个替代方案:它与string.Format或SmartFormat性能相差甚远。

使用SmartFormat.NET /2的语法仍然接近Scott在他的Code Project文章中描述的,但方法已经从"CustomFormat"更改为"Smart.Format"。SmartFormat.NET /2还有一个非常好的Wiki。这里只是一些最新语法的示例,让对更多内容产生好奇心:

命名占位符: var addrList = new[] { new { Name = "Jim", Address = new { City = "New York", State = "NY" } } }; Smart.Format("{Name} from {Address.City}, {Address.State}", addrList); // 输出:"Jim from New York, NY"

复数化: var emails = new List() { "email1", "email2", "email3" }; Smart.Format("You have {0} new {0:message|messages}", emails.Count); // 输出:"You have 3 messages"

性别变化: var user = new[] { new { Name = "John", Gender = 0 }, new { Name = "Mary", Gender = 1 } }; Smart.Default.Parser.UseAlternativeEscapeChar('\\'); Smart.Format("{1:{Name}} commented on {1:{Gender:his|her|their}} photo", user);

列表: var Users = new[] { new { Name = "Jim" }, new { Name = "Pam" }, new { Name = "Dwight" }, new { Name = "Michael" } }; Smart.Format("{Users:{Name}|, | and } liked your comment", new object[] { new { Users = Users } }); // 输出:"Jim, Pam, Dwight and Michael liked your comment"

选择: var emails = new List() { "email1", "email2", "email3" }; Smart.Format("You have {Messages.Count:choose(0|1):no new messages|a new message|{} new messages}", new object[] { new { Messages = emails } }); // 输出:"You have 3 new messages"

常见陷阱:一些SmartFormat.NET的用户遇到了一些问题和陷阱,这些在开始时了解非常有帮助。也将这些提示添加到了SmartFormat.NET /2 Wiki中。

Smart.Format与SmartFormatter.Format:当开始使用库时,请使用Smart.Format(...)。Smart.Format()会在幕后自动初始化所有需要的东西。如果直接使用SmartFormatter.Format(...),那么就需要自己初始化。所以对于初学者来说,先不要使用SmartFormatter.Format()。注意:Smart.Format(...)只是Smart.Default.Format(...)的简写。

错误处理:默认情况下,SmartFormat将格式化程序和解析器的ErrorAction设置为ErrorAction.Ignore。这可能导致令人困惑的结果。强烈建议在开发和调试代码时打开异常: Smart.Default.ErrorAction = ErrorAction.ThrowError; Smart.Default.Parser.ErrorAction = ErrorAction.ThrowError;

错误跟踪:除了抛出和捕获异常之外,还可以通过订阅相应的事件来跟踪任何格式化或解析错误。当使用Smart.Format(...)时,这些事件可以这样处理: var badPlaceholders = new HashSet(); Smart.Default.OnFormattingFailure += (sender, args) => { badPlaceholders.Add(args.Placeholder); }; var parsingErrorText = new HashSet(); Smart.Default.Parser.OnParsingFailure += (sender, args) => { parsingErrorText.Add(args.RawText); }; 这些事件无论格式化程序或解析器的ErrorAction如何设置都会触发。与异常不同,所有错误都会被报告,而不仅仅是第一个失败。这样就可以在代码中更细粒度地决定如何处理错误。

转义大括号:SmartFormat开箱即用是string.Format的替代品。其结果是,大括号会以string.Format的方式转义。所以如果期望的输出是{literal},这意味着需要将打开和关闭的大括号加倍: string.Format("{{literal}}") Smart.Format("{{literal}}") 这种string.Format兼容性在使用SmartFormat的扩展格式化功能时会导致问题,例如: // 这在默认设置下不会工作! Smart.Format("{0:{Persons:{Name}|, }}", model); 原因是格式字符串末尾的双大括号,解析器会以string.Format风格转义,导致"缺少关闭大括号"异常。幸运的是,这很容易解决: Smart.Default.Parser.UseAlternativeEscapeChar('\\'); Smart.Format("{0:{Persons:{Name}|, }}", model); 通过这个解析器设置,可以通过Smart.Format("\{literal\}")实现{literal}的输出。

多个数据源:像string.Format一样,可以使用多个数据源作为SmartFormat的参数。然而,概念有点不同: var dict1 = new Dictionary() { { "Name", "John" } }; var dict2 = new Dictionary() { { "City", "Washington" } }; result = Smart.Format("First dictionary: {0:{Name}}, second dictionary: {1:{City}}", dict1, dict2); // 输出:"First dictionary: John, second dictionary: Washington"

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485