SmartFormat.NET /2 - Enhancing string.Format to new levels

SmartFormat.NET /2 is a string template library that allows you to fill a string with data. Easy to use, fast, extensible, and extremely powerful. Allows for named {placeholders} using any data type, conditional formatting, iterating through IEnumerables, and much more.

Introduction

When it was time for an improved release of MailMergeLib - A .NET Mail Client Library I was looking for a fast string template library, which should substitute the low performing regular expressions that were used for replacing the placeholders in the mail text with variable values. I had a closer look at several solutions like this one or that one. But none of them came close to what Scott Rippey came up with some time ago: SmartFormat.NET. Having ten-thousands of downloads from NuGet this library is also quite popular.

Unfortunately Scott and his project contributors had different priorities for some time, and so the project was kind of dormant for a while. So I decided to contribute to the project, to take care of the issues that were reported by users, to port it to .Net Core (besides .NET Framework 4.0 and 4.5), and to extend it where I felt some deficiencies.

The sample already includes a stop watch, which will make very clear, that this is not an alternative: It's a "dimension" away from string.Format or SmartFormat performance.

Using the code

The syntax for using SmartFormat.NET /2 is still close to what Scott had described in his article here on Code Project, but the method has changed from "CustomFormat" to "Smart.Format". And SmartFormat.NET /2 has a very good Wiki as well. Here are just a few examples with the up-to-date syntax to make you curious for more:

Common Pitfalls

Some users of SmartFormat.NET came across some issues and pitfalls with are extremely helpful to know when getting started. I also added these hints to the SmartFormat.NET /2 Wiki.

Smart.Format vs. SmartFormatter.Format

When you start to use the library, us Smart.Format(...). Smart.Format() automatically initializes all you need behind the scenes. If you're using SmartFormatter.Format(...) directly, then it's your job to initialize. So for the beginning just leave SmartFormatter.Format() alone. Note: Smart.Format(...) is just the short version for Smart.Default.Format(...).

Error Handling

By default, SmartFormat sets ErrorAction for formatter and parser to ErrorAction.Ignore. This can lead to confusing results. It's highly recommended, to turn exceptions on while developing and debugging the code:

Error Tracking

Besides throwing and catching exceptions it is possible, to trace any formatting or parsing errors by subscribing to corresponding events. When using Smart.Format(...) these events can be processed like this:

These events fire no matter how ErrorAction of the formatter or parser are set. Opposed to exceptions, all errors will be reported, not only the first failure. Going this way you can decide in your code more fine grained, how to deal with errors.

Escaping Curly Braces

Out of the box SmartFormat is a drop-in replacement for string.Format. The consequence is, that curly braces are escaped the string.Format way. So if the desired output shall be {literal}, it means doubling the open and closing curly braces:

string.Format("{{literal}}")
Smart.Format("{{literal}}")

This string.Format compatibility, however, causes problems when using SmartFormat's extended formatting capabilities, like

// This won't work with default settings!
Smart.Format("{0:{Persons:{Name}|, }}", model);

The reason is the double curly braces at the end of the format string, which the parser will escape in the string.Format style, leading to a "missing closing brace" exception. Luckily, this is easy to solve:

// This will work
Smart.Default.Parser.UseAlternativeEscapeChar('\\');
Smart.Format("{0:{Persons:{Name}|, }}", model);

With this parser setting the output {literal} can be achieved by Smart.Format("\{literal\}").

Several Data Sources

Like with string.Format it is possible to use several data sources as parameters to SmartFormat. The concept however is a bit different:

Security

When running on .NET Framework 4.x, SmartFormat is using System.Runtime.Remoting.Messaging.CallContext.LogicalGet|SetData in its ListFormatter. With this in place, we know that dotnetfiddle.net will throw a security exception. When compiling SmartFormat for .Net Core, LogicalGet|SetData is replaced by AsyncLocal<T>, which does not have this issue. Unfortunately AsyncLocal<T> is supported only on .NET Framework version 4.6 or later.

History

2016-12-04: Initial article for SmartFormat.NET /2

License

This article, along with any associated source code and files, is licensed under The MIT License

Indeed, reflection is a pain performance-wise without caching. The freedom to process virtually any type as an argument to SmartFormat has its price.
Fortunately you can enable caching using SmartFormat like this:

Smart.Default.FormatWithCache(ref cache, format, args)

Although this still does not compare to native member access, maybe you like the concept even more now