c# 6 introduced string interpolation syntax. This feature allows for formatted text to be easily declared in code. For example:
var name = "World"; var message = $"Hello, {name}"; // "Hello, World"
At it's most basic, this is syntactic sugar on top of c#'s string.Format
method, but with a much cleaner syntax. However, simply interpolating strings is often a dangerous proposition. Consider the following:
var url = $"https://api.example.com/sample?arg={arg}";
This will work in most cases, but if the parameter includes non-url-safe characters, this is likely to break. This could even expose a serious security vulnerability, expecially if you're doing string interpolation for HTML, javascript, or SQL (don't ever do this!).
Formattable String
Hidden deep in the c# language spec is a minor note about string interpolation. In general, interpolated strings are compiled to a call to string.Format
, but you can also cast it to a FormattableString
. This type represents the string template and an array of objects which will be interpolated into it.
var make = "Chrysler"; var model = "Town & Country"; var url = (FormattableString)$"https://api.example.com/vehicles?make={make}&model={model}"; Console.WriteLine(url.Format); // "https://api.example.com/vehicles?make={0}&model={1}"; Console.WriteLine(url.GetArgument(0)); // "Chrysler"; Console.WriteLine(url.GetArgument(1)); // "Town & Country";
This provides some interesting opportunities to create much more powerful string templating tools. For example, if we wanted to automatically encode the URL arguments, we can do the following:
public static class Format { public static Uri Uri(FormattableString template) { var encodedArgs = new object[template.ArgumentCount]; for (var i = 0; i < template.ArgumentCount; i++) { var original = template.GetArgument(i); encodedArgs[i] = HttpUtility.UrlEncode(original); } return new Uri(string.Format(template.Format, encodedArgs)); } }
The above code creates a new array and populates it with the url-encoded arguments. It then calls string.Format
with the original template and new encoded arguments and returns it as a Uri to indicate that it's been safely encoded.
to use it, we can call
var make = "Chrysler"; var model = "Town & Country"; var url = Format.Uri($"https://api.example.com/vehicles?make={make}&model={model}"); Console.WriteLine(url); // https://api.example.com/vehicles?make=Chrysler&model=Town+%26+Country
Custom Formats
Another interesting feature we can make (ab)use of is custom format strings. In a traditional c# string template, you can specify a format for each argument, I.E.
Console.WriteLine($"Today is {DateTime.Now:yyyy-MM-dd}"); // Today is 2019-12-13
This is effectively the equivelant of calling dateTime.ToString("yyyy-MM-dd")
. Any object that implements IFormattable
can be used with a custom format string, which gives us an opportunity to define a simple syntax when working with string templates. In this example, we'll set up a simple HTML template that will either html encode a value or format it as markdown.
public static HtmlString Html(FormattableString template) { var encodedArgs = new object[template.ArgumentCount]; for (var i = 0; i < template.ArgumentCount; i++) { encodedArgs[i] = new HtmlArgument(template.GetArgument(i)); } return new HtmlString(string.Format(template.Format, encodedArgs)); } class HtmlArgument : IFormattable { public HtmlArgument(object value) { Value = value; } public object Value { get; } public string ToString(string format, IFormatProvider formatProvider) { switch (format) { case "markdown": return new Markdown().Transform(Value.ToString()); case "dangerous-raw-html": return Value.ToString(); default: return HttpUtility.HtmlEncode(Value); } } }
We can then use this as follows:
var html = Format.Html($"<article><h1>{title}</h1>{content:markdown}</article>");
title
will be safely HTML encoded, and content
will be rendered as markdown.
Wrapping Up
String interpolation in c# is convenient, but can lead to some traps. If not used carefully, it can break with edge cases or even introduce vulnerabilities. Formattable strings are a little known, but potentially quite useful feature in c# that can be used to make string interpolation smarter and more context-aware.
Top comments (4)
Wow! I learned something new today! Thanks for sharing
Actually, interpolated strings was introduced in c# 6, not 7. However, I didn't know about FormattableStrings, so thanks!
Totally right. Fixed.
Thanks for your this great article!