Chapter 9. Stringy features

This chapter covers

  • Using interpolated string literals for more-readable formatting
  • Working with FormattableString for localization and custom formatting
  • Using nameof for refactoring-friendly references

Everyone knows how to use strings. If string isn’t the first .NET data type you learned about, it’s probably the second. The string class itself hasn’t changed much over the course of .NET’s history, and not many string-oriented features have been introduced in C# as a language since C# 1. C# 6, however, changed that with another kind of string literal and a new operator. You’ll look at both of these in detail in this chapter, but it’s worth remembering that the strings themselves haven’t changed at all. Both features provide new ways of obtaining strings, but that’s all.

Just like the features you saw in chapter 8, string interpolation doesn’t allow you to do anything you couldn’t do before; it just allows you to do it more readably and concisely. That’s not to diminish the importance of the feature. Anything that allows you to write clearer code more quickly—and then read it more quickly later—will make you more productive.

The nameof operator was genuinely new functionality in C# 6, but it’s a reasonably minor feature. All it does is allow you to get an identifier that already appears in your code but as a string at execution time. It’s not going to change your world like LINQ or async/await, but it helps avoid typos and allows refactoring tools to do more work for you. Before I show you anything new, let’s revisit what you already know.

9.1. A recap on string formatting in .NET

You almost certainly know everything in this section. You may well have been using strings for many years and almost certainly for as long as you’ve been using C#. Still, in order to understand how the interpolated string literal feature in C# 6 works, it’s best to have all that knowledge uppermost in your mind. Please bear with me as we go over the basics of how .NET handles string formatting. I promise we’ll get to the new stuff soon.

9.1.1. Simple string formatting

If you’re like me, you like experimenting with new languages by writing trivial console applications that do nothing useful but give the confidence and firm foundation to move on to more-impressive feats. As such, I can’t remember how many languages I’ve used to implement the functionality shown next—asking the user’s name and then saying hello to that user:

Console.Write("What's your name? ");
string name = Console.ReadLine();
Console.WriteLine("Hello, {0}!", name);

The last line is the most relevant one for this chapter. It uses an overload of Console.WriteLine, which accepts a composite format string including format items and then arguments to replace those format items. The preceding example has one format item, {0}, which is replaced by the value of the name variable. The number in the format item specifies the index of the argument you want to fill the hole (where 0 represents the first of the values, 1 represents the second, and so on).

This pattern is used in various APIs. The most obvious example is the static Format method in the string class, which does nothing but format the string appropriately. So far, so good. Let’s do something a little more complicated.

9.1.2. Custom formatting with format strings

Just to be clear, my motivation for including this subsection is as much for my future self as for you, dear reader. If MSDN displayed the number of times I’ve visited any given page, the number for the page on composite format strings would be frightening. I keep forgetting exactly what goes where and what terms to use, and I figured that if I included that information here, I might start remembering it better. I hope you find it helpful in the same way.

Each format item in a composite format string specifies the index of the argument to be formatted, but it can also specify the following options for formatting the value:

  • An alignment, which specifies a minimum width and whether the value should be left- or right-aligned. Right-alignment is indicated by a positive value; left-alignment is indicated by a negative value.
  • A format string for the value. This is probably used most often for date and time values or numbers. For example, to format a date according to ISO-8601, you could use a format string of yyyy-MM-dd. To format a number as a currency value, you could use a format string of C. The meaning of the format string depends on the type of value being formatted, so you need to look up the relevant documentation to choose the right format string.

Figure 9.1 shows all the parts of a composite format string you could use to display a price.

Figure 9.1. A composite format string with a format item to display a price

The alignment and the format string are independently optional; you can specify either, both, or neither. A comma in the format item indicates an alignment, and a colon indicates a format string. If you need a comma in the format string, that’s fine; there’s no concept of a second alignment value.

As a concrete example to expand on later, let’s use the code from figure 9.1 in a broader context, showing different lengths of results to demonstrate the point of alignment. Listing 9.1 displays a price ($95.25), tip ($19.05), and total ($114.30), lining up the labels on the left and the values on the right.

The output on a machine using the US English culture settings by default, would look like this:

Price:    $95.25
Tip:      $19.05
Total:   $114.30

To make the values right-aligned (or left-padded with spaces, to look at it the other way around), the code uses an alignment value of 9. If you had a huge bill (a million dollars, for example), the alignment would have no effect; it specifies only a minimum width. If you wanted to write code that right-aligned every possible set of values, you’d have to work out how wide the biggest one would be first. That’s pretty unpleasant code, and I’m afraid nothing in C# 6 makes it easier.

Listing 9.1. Displaying a price, tip, and total with values aligned
decimal price = 95.25m;
decimal tip = price * 0.2m;                       1
Console.WriteLine("Price: {0,9:C}", price);
Console.WriteLine("Tip:   {0,9:C}", tip);
Console.WriteLine("Total: {0,9:C}", price + tip);

  • 1 20% tip

When I showed the output of listing 9.1 on a machine in the US English culture, the part about the culture was important. On a machine using a UK English culture, the code would use £ signs instead. On a machine in the French culture, the decimal separator would become a comma, the currency sign would become a Euro symbol, and that symbol would be at the end of the string instead of the start! Such are the joys of localization, which you’ll look at next.

9.1.3. Localization

In broad terms, localization is the task of making sure your code does the right thing for all your users, no matter where they are in the world. Anyone who claims that localization is simple is either much more experienced at it than I am or hasn’t done enough of it to see how painful it can be. Considering the world is basically round, it certainly seems to have a lot of nasty corner cases to handle. Localization is a pain in all programming languages, but each has a slightly different way of addressing the problems.

Note

Although I use the term localization in this section, other people may prefer the term globalization. Microsoft uses the two terms in a slightly different way than other industry bodies, and the difference is somewhat subtle. Experts, please forgive the hand-waving here; the big picture is more important than the fine details of terminology, just this once.

In .NET, the most important type to know about for localization purposes is CultureInfo. This is responsible for the cultural preferences of a language (such as English), or a language in a particular location (such as French in Canada), or a particular variant of a language in a location (such as simplified Chinese as used in Taiwan). These cultural preferences include various translations (the words used for the days of the week, for example) and indicate how text is sorted and how numbers are formatted (whether to use a period or comma as the decimal separator) and much more.

Often, you won’t see CultureInfo in a method signature, but instead the IFormatProvider interface, which CultureInfo implements. Most formatting methods have overloads with an IFormatProvider as the first parameter before the format string itself. For example, consider these two signatures from string.Format:

static string Format(IFormatProvider provider,
    string format, params object[] args)
static string Format(string format, params object[] args)

Usually, if you provide overloads that differ only by a single parameter, that parameter is the last one, so you might have expected the provider parameter to come after args. That wouldn’t work, though, because args is a parameter array (it uses the params modifier). If a method has a parameter array, that has to be the final parameter.

Even though the parameter is of type IFormatProvider, the value you pass in as an argument is almost always CultureInfo. For example, if you want to format my date of birth for US English—June 19, 1976—you could use this code:

var usEnglish = CultureInfo.GetCultureInfo("en-US");
var birthDate = new DateTime(1976, 6, 19);
string formatted = string.Format(usEnglish, "Jon was born on {0:d}", birthDate);

Here, d is the standard date/time format specifier for short date, which in US English corresponds to month/day/year. My date of birth would be formatted as 6/19/1976, for example. In British English, the short date format is day/month/year, so the same date would be formatted as 19/06/1976. Notice that not just the ordering is different: the month is 0-padded to two digits in the British formatting, too.

Other cultures can use entirely different formatting. It can be instructive to see just how different the results of formatting the same value can be between cultures. For example, you could format the same date in every culture .NET knows about as shown in the next listing.

Listing 9.2. Formatting a single date in every culture
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures);
var birthDate = new DateTime(1976, 6, 19);
foreach (var culture in cultures)
{
    string text = string.Format(
        culture, "{0,-15} {1,12:d}", culture.Name, birthDate);
    Console.WriteLine(text);
}

The output for Thailand shows that I was born in 2519 in the Thai Buddhist calendar, and the output for Afghanistan shows that I was born in 1355 in the Islamic calendar:

...
tg-Cyrl           19.06.1976
tg-Cyrl-TJ        19.06.1976
th                 19/6/2519
th-TH              19/6/2519
ti                19/06/1976
ti-ER             19/06/1976
...
ur-PK             19/06/1976
uz                19/06/1976
uz-Arab           29/03 1355
uz-Arab-AF        29/03 1355
uz-Cyrl           19/06/1976
uz-Cyrl-UZ        19/06/1976
...

This example also shows a negative alignment value to left-align the culture name using the {0,-15} format item while keeping the date right-aligned with the {1,12:d} format item.

Formatting with the default culture

If you don’t specify a format provider, or if you pass null as the argument corresponding to an IFormatProvider parameter, CultureInfo.CurrentCulture will be used as a default. Exactly what that means will depend on your context; it can be set on a per thread basis, and some web frameworks will set it before processing a request on a particular thread.

All I can advise about using the default is to be careful: make sure you know that the value in your specific thread will be appropriate. (Checking the exact behavior is particularly worthwhile if you start parallelizing operations across multiple threads, for example.) If you don’t want to rely on the default culture, you’ll need to know the culture of the end user you need to format the text for and do so explicitly.

Formatting for machines

So far, we’ve assumed that you’re trying to format the text for an end user. But that’s often not the case. For machine-to-machine communication (such as in URL query parameters to be parsed by a web service), you should use the invariant culture, which is obtained via the static CultureInfo.InvariantCulture property.

For example, suppose you were using a web service to fetch the list of best sellers from a publisher. The web service might use a URL of https://manning.com/webservices/bestsellers but allow a query parameter called date to allow you to find out the best-selling books on a particular date.[1] I’d expect that query parameter to use an ISO-8601 format (year first, using dashes between the year, month, and day) for the date. For example, if you wanted to retrieve the best-selling books as of the start of March 20, 2017, you’d want to use a URL of https://manning.com/webservices/bestsellers?date=2017-03-20. To construct that URL in code in an application that allows the user to pick a specific date, you might write something like this:

1

This is a fictional web service, as far as I’m aware.

string url = string.Format(
    CultureInfo.InvariantCulture,
    "{0}?date={1:yyyy-MM-dd}",
    webServiceBaseUrl,
    searchDate);

Most of the time, you shouldn’t be directly formatting data for machine-to-machine communication yourself, mind you. I advise you to avoid string conversions wherever you can; they’re often a code smell showing that either you’re not using a library or framework properly or that you have data design issues (such as storing dates in a database as text instead of as a native date/time type). Having said that, you may well find yourself building strings manually like this more often than you’d like; just pay attention to which culture you should be using.

Okay, that was a long introduction. But with all this formatting information buzzing around your brain and somewhat ugly examples niggling at you, you’re in the right frame of mind to welcome interpolated string literals in C# 6. All those calls to string.Format look unnecessarily long-winded, and it’s annoying having to look between the format string and the argument list to see what will go where. Surely, we can make our code clearer than that.

9.2. Introducing interpolated string literals

Interpolated string literals in C# 6 allow you to perform all this formatting in a much simpler way. The concepts of a format string and arguments still apply, but with interpolated string literals, you specify the values and their formatting information inline, which leads to code that’s much easier to read. If you look through your code and find a lot of calls to string.Format using hardcoded format strings, you’ll love interpolated string literals.

String interpolation isn’t a new idea. It’s been in many programming languages for a long time, but I’ve never felt it to be as neatly integrated as it is in C#. That’s particularly remarkable when you consider that adding a feature into a language when it’s already mature is harder than building it into the first version.

In this section, you’ll look at some simple examples before exploring interpolated verbatim string literals. You’ll learn how localization can be applied using FormattableString and then take a closer look at how the compiler handles interpolated string literals. We’ll round off the section with discussion about where this feature is most useful as well as its limitations.

9.2.1. Simple interpolation

The simplest way to demonstrate interpolated string literals in C# 6 is to show you the equivalent to the earlier example in which we asked for the user’s name. The code doesn’t look hugely different; in particular, only the last line has changed at all.

C# 5—old-style style formatting

C# 6—interpolated string literal

Console.Write("What's your name? ");
string name = Console.ReadLine();
Console.WriteLine("Hello, {0}!",
    name);
Console.Write("What's your name? ");
string name = Console.ReadLine();
Console.WriteLine($"Hello, {name}!");

The interpolated string literal is shown in bold. It starts with a $ before the opening double quote; that’s what makes it an interpolated string literal rather than a regular one, as far as the compiler is concerned. It contains {name} instead of {0} for the format item. The text in the braces is an expression that’s evaluated and then formatted within the string. Because you’ve provided all the information you need, the second argument to WriteLine isn’t required anymore.

Note

I’ve lied a little here, for the sake of simplicity. This code doesn’t work quite the same way as the original code. The original code passed all the arguments to the appropriate Console.WriteLine overload, which performed the formatting for you. Now, all the formatting is performed with a string.Format call, and then the Console.WriteLine call uses the overload, which has just a string parameter. The result will be the same, though.

Just as with expression-bodied members, this doesn’t look like a huge improvement. For a single format item, the original code doesn’t have a lot to be confused by. The first couple of times you see this, it might even take you a little longer to read an interpolated string literal than a string formatting call. I was skeptical about just how much I’d ever like them, but now I often find myself converting pieces of old code to use them almost automatically, and I find the readability improvement is often significant.

Now that you’ve seen the simplest example, let’s do something a bit more complex. You’ll follow the same sequence as before, first looking at controlling the formatting of values more carefully and then considering localization.

9.2.2. Format strings in interpolated string literals

Good news! There’s nothing new to learn here. If you want to provide an alignment or a format string with an interpolated string literal, you do it the same way you would in a normal composite format string: you add a comma before the alignment and a colon before the format string. Our earlier composite formatting example changes in the obvious way, as shown in the following listing.

Listing 9.3. Aligned values using interpolated string literals
decimal price = 95.25m;
decimal tip = price * 0.2m;                       1
Console.WriteLine($"Price: {price,9:C}");         2
Console.WriteLine($"Tip:   {tip,9:C}");           2
Console.WriteLine($"Total: {price + tip,9:C}");   2

  • 1 20% tip
  • 2 Right-justify prices using nine-digit alignment

Note that in the last line, the interpolated string doesn’t just contain a simple variable for the argument; it performs the addition of the tip to the price. The expression can be any expression that computes a value. (You can’t just call a method with a void return type, for example.) If the value implements the IFormattable interface, its ToString(string, IFormatProvider) method will be called; otherwise, System.Object.ToString() is used.

9.2.3. Interpolated verbatim string literals

You’ve no doubt seen verbatim string literals before; they start with @ before the double quote. Within a verbatim string literal, backslashes and line breaks are included in the string. For example, in the verbatim string literal @"c:Windows", the backslash really is a backslash; it isn’t the start of an escape sequence. The only kind of escape sequence within a verbatim string literal is when you have two double quote characters together, which results in one double quote character in the resulting string. Verbatim string literals are typically used for the following:

  • Strings breaking over multiple lines
  • Regular expressions (which use backslashes for escaping, quite separate from the escaping the C# compiler uses in regular string literals)
  • Hardcoded Windows filenames
Note

With multiline strings, you should be careful about exactly which characters end up in your string. Although the difference between “carriage-return” and “carriage-return line-feed separators” is irrelevant in most code, it’s significant in verbatim string literals.

The following shows a quick example of each of these:

string sql = @"                                        1
  SELECT City, ZipCode                                 1
  FROM Address                                         1
  WHERE Country = 'US'";                               1
Regex lettersDotDigits = new Regex(@"[a-z]+.d+");    2
string file = @"c:usersskeetTestTest.cs"           3

  • 1 SQL is easier to read when split over multiple lines.
  • 2 Backslashes are common in regular expressions.
  • 3 Windows filename

Verbatim string literals can be interpolated as well; you put a $ in front of the @, just as you would to interpolate a regular string literal. Our earlier multiline output could be written using a single interpolated verbatim string literal, as shown in the following listing.

Listing 9.4. Aligned values using a single interpolated verbatim string literal
decimal price = 95.25m;
decimal tip = price * 0.2m;              1
Console.WriteLine($@"Price: {price,9:C}
Tip:   {tip,9:C}
Total: {price + tip,9:C}");

  • 1 20% tip

I probably wouldn’t do this; it’s just not as clean as using three separate statements. I’m using the preceding code only as a simple example of what’s possible. Consider it for places where you’re already using verbatim string literals sensibly.

Tip

The order of the symbols matters. $@"Text" is a valid interpolated verbatim string literal, but @$"Text" isn’t. I admit I haven’t found a good mnemonic device to remember this. Just try whichever way you think is right, and change it if the compiler complains!

This is all very convenient, but I’ve shown only the surface level of what’s going on. I’ll assume you bought this book because you want to know about the features inside and out.

9.2.4. Compiler handling of interpolated string literals (part 1)

The compiler transformation here is simple. It converts the interpolated string literal into a call to string.Format, and it extracts the expressions from the format items and passes them as arguments after the composite format string. The expression is replaced with the appropriate index, so the first format item becomes {0}, the second becomes {1}, and so on.

To make this clearer, let’s consider a trivial example, this time separating the formatting from the output for clarity:

int x = 10;
int y = 20;
string text = $"x={x}, y={y}";
Console.WriteLine(text);

This is handled by the compiler as if you’d written the following code instead:

int x = 10;
int y = 20;
string text = string.Format("x={0}, y={1}", x, y);
Console.WriteLine(text);

The transformation is that simple. If you want to go deeper and verify it for yourself, you could use a tool such as ildasm to look at the IL that the compiler has generated.

One side effect of this transformation is that unlike regular or verbatim string literals, interpolated string literals don’t count as constant expressions. Although in some cases the compiler could reasonably consider them to be constant (if they don’t have any format items or if all the format items are just string constants without any alignment or format strings), these would be corner cases that would complicate the language for little benefit.

So far, all our interpolated strings have resulted in a call to string.Format. That doesn’t always happen, though, and for good reasons, as you’ll see in the next section.

9.3. Localization using FormattableString

In section 9.1.3, I demonstrated how string formatting can take advantage of different format providers—typically using CultureInfo—to perform localization. All the interpolated string literals you’ve seen so far would’ve been evaluated using the default culture for the executing thread, so our price examples in 9.1.2 and 9.2.2 could easily have different output on your machine than the result I showed.

To perform formatting in a specific culture, you need three pieces of information:

  • The composite format string, which includes the hardcoded text and the format items as placeholders for the real values
  • The values themselves
  • The culture you want to format the string in

You can slightly rewrite our first example of formatting in a culture to store each of these in a separate variable, and then call string.Format at the end:

var compositeFormatString = "Jon was born on {0:d}";
var value = new DateTime(1976, 6, 19);
var culture = CultureInfo.GetCultureInfo("en-US");
var result = string.Format(culture, compositeFormatString, value);

How can you do this with interpolated string literals? An interpolated string literal contains the first two pieces of information (the composite format string and the values to format), but there’s nowhere to put the culture. That would be fine if you could get at the individual pieces of information afterward, but every use of interpolated string literals that you’ve seen so far has performed the string formatting as well, leaving you with just a single string as the result.

That’s where FormattableString comes in. This is a class in the System namespace introduced in .NET 4.6 (and .NET Standard 1.3 in the .NET Core world). It holds the composite format string and the values so they can be formatted in whatever culture you want later. The compiler is aware of FormattableString and can convert an interpolated string literal into a FormattableString instead of a string where necessary. That allows you to rewrite our simple date-of-birth example as follows:

var dateOfBirth = new DateTime(1976, 6, 19);
FormattableString formattableString =
    $"Jon was born on {dateofBirth:d}";                1
var culture = CultureInfo.GetCultureInfo("en-US");
var result = formattableString.ToString(culture);      2

  • 1 Keeps the composite format string and value in a FormattableString
  • 2 Formats in the specified culture

Now that you know the basic reason for the existence of FormattableString, you can look at how the compiler uses it and then examine localization in more detail. Although localization is certainly the primary motivation for FormattableString, it can be used in other situations as well, which you’ll look at in section 9.3.3. The section then concludes with your options if your code is targeting an earlier version of .NET.

9.3.1. Compiler handling of interpolated string literals (part 2)

In a reversal of my earlier approach, this time it makes sense to talk about how the compiler considers FormattableString before moving on to examining its uses in detail. The compile-time type of an interpolated string literal is string. There’s no conversion from string to FormattableString or to IFormattable (which FormattableString implements), but there are conversions from interpolated string literal expressions to both FormattableString and IFormattable.

The differences between conversions from an expression to a type and conversions from a type to another type are somewhat subtle, but this is nothing new. For example, consider the integer literal 5. Its type is int, so if you declare var x = 5, the type of x will be int, but you can also use it to initialize a variable of type byte. For example, byte y = 5; is perfectly valid. That’s because the language specifies that for constant integer expressions (including integer literals) within the range of byte, there’s an implicit conversion from the expression to byte. If you can get your head around that, you can apply the exact same idea to verbatim string literals.

When the compiler needs to convert an interpolated string literal into a FormattableString, it performs most of the same steps as for a conversion to string. But instead of string.Format, it calls the static Create method on the System.Runtime.CompilerServices.FormattableStringFactory class. This is another type introduced at the same time as FormattableString. To go back to an earlier example, say you have this source code:

int x = 10;
int y = 20;
FormattableString formattable = $"x={x}, y={y}";

That’s handled by the compiler as if you’d written the following code instead (with the appropriate namespaces, of course):

int x = 10;
int y = 20; 
FormattableString formattable = FormattableStringFactory.Create(
    "x={0}, y={1}", x, y);

FormattableString is an abstract class with members as shown in the following listing.

Listing 9.5. Members declared by FormattableString
public abstract class FormattableString : IFormattable
{
    protected FormattableString();
    public abstract object GetArgument(int index);
    public abstract object[] GetArguments();
    public static string Invariant(FormattableString formattable);
    string IFormattable.ToString
        (string ignored, IFormatProvider formatProvider);
    public override string ToString();
    public abstract string ToString(IFormatProvider formatProvider);
    public abstract int ArgumentCount { get; }
    public abstract string Format { get; }
}

Now that you know when and how FormattableString instances are built, let’s see what you can do with them.

9.3.2. Formatting a FormattableString in a specific culture

By far, the most common use for FormattableString will be to perform the formatting in an explicitly specified culture instead of in the default culture for the thread. I expect that most uses will be for a single culture: the invariant culture. This is so common that it has its own static method: Invariant. Calling this is equivalent to passing CultureInfo.InvariantCulture into the ToString(IFormatProvider) method, which behaves exactly as you’d expect. But making Invariant a static method means it’s simpler to call as a subtle corollary of the language details you just looked at in section 9.3.1. The fact that it takes FormattableString as a parameter means you can just use an interpolated string literal as an argument, and the compiler knows that it has to apply the relevant conversion; there’s no need for a cast or a separate variable.

Let’s consider a concrete example to make it clear. Suppose you have a DateTime value and you want to format just the date part of it in ISO-8601 format as part of a URL query parameter for machine-to-machine communication. You want to use the invariant culture to avoid any unexpected results from using the default culture.

Note

Even when you specify a custom format string for a date and time, and even when that custom format uses only digits, the culture still has an impact. The biggest one is that the value is represented in the default calendar system for the culture. If you format October 21, 2016 (Gregorian) in the culture ar-SA (Arabic in Saudi Arabia), you’ll get a result with a year of 1438.

You can do this formatting in four ways, all of which are shown together in the following listing. All four approaches give exactly the same result, but I’ve shown all of them to demonstrate how the multiple language features work together to give a clean final option.

Listing 9.6. Formatting a date in the invariant culture
DateTime date = DateTime.UtcNow;

string parameter1 = string.Format(                         1
    CultureInfo.InvariantCulture,                          1
    "x={0:yyyy-MM-dd}",                                    1
    date);

string parameter2 =                                        2
    ((FormattableString)$"x={date:yyyy-MM-dd}")            2
    .ToString(CultureInfo.InvariantCulture);               2

string parameter3 = FormattableString.Invariant(           3
    $"x={date:yyyy-MM-dd}");                               3

string parameter4 = Invariant($"x={date:yyyy-MM-dd}");     4

  • 1 Old-school formatting with string.Format
  • 2 Casting to FormattableString and calling ToString(IFormatProvider)
  • 3 Regular call to FormattableString.Invariant
  • 4 Shortened call to FormattableString.Invariant

The main interesting difference is between the initializers for parameter2 and parameter3. To make sure you have a FormattableString for parameter2 rather than just a string, you have to cast the interpolated string literal to that type. An alternative would’ve been to declare a separate local variable of type FormattableString, but that would’ve been about as long-winded. Compare that with the way parameter3 is initialized, which uses the Invariant method that accepts a parameter of type FormattableString. That allows the compiler to infer that you want to use the implicit conversion from an interpolated string literal to FormattableString, because that’s the only way that the call will be valid.

I’ve cheated for parameter4. I’ve used a feature you haven’t seen yet, making static methods from a type available with a using static directive. You can flick forward to the details later (section 10.1.1) or trust me for now that it works. You just need using static System.FormattableString in your list of using directives.

Formatting in a noninvariant culture

If you want to format a FormattableString in any culture other than the invariant one, you need to use one of the ToString methods. In most cases, you’ll want to call the ToString(IFormatProvider) overload directly. As a slightly shorter example than you saw earlier, here’s code to format the current date and time in US English using the “general date/time with short time” standard format string ("g"):

FormattableString fs = $"The current date and time is: {DateTime.Now:g}";
string formatted = fs.ToString(CultureInfo.GetCultureInfo("en-US"));

Occasionally, you may want to pass the FormattableString to another piece of code to perform the final formatting step. In that case, it’s worth remembering that FormattableString implements the IFormattable interface, so any method accepting an IFormattable will accept a FormattableString. The FormattableString implementation of IFormattable.ToString(string, IFormatProvider) ignores the string parameter because it already has everything it needs: it uses the IFormatProvider parameter to call the ToString(IFormatProvider) method.

Now that you know how to use cultures with interpolated string literals, you may be wondering why the other members of FormattableString exist. In the next section, you’ll look at one example.

9.3.3. Other uses for FormattableString

I’m not expecting FormattableString to be widely used outside the culture scenario I showed in section 9.3.2, but it’s worth considering what can be done. I’ve chosen this example as one that’s immediately recognizable and elegant in its own way, but I wouldn’t go so far as to recommend its use. Aside from the code presented here lacking validation and some features, it may give the wrong impression to a casual reader (and to static code analysis tools). By all means, pursue it as an idea, but use appropriate caution.

Most developers are aware of SQL injection attacks as a security vulnerability, and many know the common solution in the format of parameterized SQL. Listing 9.7 shows what you don’t want to do. If a user enters a value containing an apostrophe, they have huge amounts of power over your database. Imagine that you have a database with entries of some kind that a user can add a tag to partitioned by user identifier. You’re trying to list all the descriptions for a user-specified tag restricted to that user.

Listing 9.7. Awooga! Awooga! Do not use this code!
var tag = Console.ReadLine();                           1
using (var conn = new SqlConnection(connectionString))
{
    conn.Open();
    string sql =
        $@"SELECT Description FROM Entries              2
           WHERE Tag='{tag}' AND UserId={userId}";      2
    using (var command = new SqlCommand(sql, conn))
    {
        using (var reader = command.ExecuteReader())    3
        {
            ...                                         4
        }
    }
}

  • 1 Reads arbitrary data from the user
  • 2 Builds SQL dynamically including user input
  • 3 Executes the untrustworthy SQL
  • 4 Uses the results

Most SQL injection vulnerabilities I’ve seen in C# use string concatenation rather than string formatting, but it’s the same deal. It mixes code (SQL) and data (the value the user entered) in an alarming way.

I’m going to assume that you know how you’d have fixed this problem in the past using parameterized SQL and calling command.Parameters.Add(...) appropriately. Code and data are suitably separated, and life is good again. Unfortunately, that safe code doesn’t look as appealing as the code in listing 9.7. What if you could have it both ways? What if you could write SQL that made it obvious what you were trying to do but was still safely parameterized? With FormattableString, you can do exactly that.

You’ll work backward, from our desired user code, through the implementation that enables it. The following listing shows the soon-to-be-safe equivalent of listing 9.7.

Listing 9.8. Safe SQL parameterization using FormattableString
var tag = Console.ReadLine();                               1
using (var conn = new SqlConnection(connectionString))
{
    conn.Open();
    using (var command = conn.NewSqlCommand(                2
        $@"SELECT Description FROM Entries                  2
           WHERE Tag={tag:NVarChar}                         2
           AND UserId={userId:Int}"))                       2
    {
        using (var reader = command.ExecuteReader())        3
        {
            // Use the data                                 4
        }
    }
}

  • 1 Reads arbitrary data from the user
  • 2 Builds a SQL command from the interpolated string literal
  • 3 Executes the QL safely
  • 4 Uses the results

Most of this listing is identical to listing 9.7. The only difference is in how you construct the SqlCommand. Instead of using an interpolated string literal to format the values into SQL and then passing that string into the SqlCommand constructor, you’re using a new method called NewSqlCommand, which is an extension method you’ll write soon. Predictably, the second parameter of that method isn’t string but FormattableString. The interpolated string literal no longer has apostrophes around {tag}, and you’ve specified each parameter’s database type as a format string. That’s certainly unusual. What is it doing?

First, let’s think about what the compiler is doing for you. It’s splitting the interpolated string literal into two parts: a composite format string and the arguments for the format items. The composite format string the compiler creates will look like this:

SELECT Description FROM Entries
WHERE Tag={0:NVarChar} AND UserId={1:Int}

You want SQL that ends up looking like this instead:

SELECT Description FROM Entries
WHERE Tag=@p0 AND UserId=@p1

That’s easy enough to do; you just need to format the composite format string, passing in arguments that will evaluate to "@p0" and "@p1". If the type of those arguments implements IFormattable, calling string.Format will pass the NVarChar and Int format strings as well, so you can set the types of the SqlParameter objects appropriately. You can autogenerate the names, and the values come directly from the FormattableString.

It’s highly unusual to make an IFormattable.ToString implementation have side effects, but you’re using only this format-capturing type for this single call, and you can keep it safely hidden from any other code. The following listing is a complete implementation.

Listing 9.9. Implementing safe SQL formatting
public static class SqlFormattableString
{
    public static SqlCommand NewSqlCommand(                              
        this SqlConnection conn,FormattableString formattableString)    
    {
        SqlParameter[] sqlParameters = formattableString.GetArguments()  
            .Select((value, position) =>                                 
                new SqlParameter(Invariant($"@p{position}"), value))     
            .ToArray();                                                  
        object[] formatArguments = sqlParameters                         
            .Select(p => new FormatCapturingParameter(p))                
            .ToArray();                                                  
        string sql = string.Format(formattableString.Format,
            formatArguments);
        var command = new SqlCommand(sql, conn);                         
        command.Parameters.AddRange(sqlParameters);                      
        return command;
    }

    private class FormatCapturingParameter : IFormattable                
    {
        private readonly SqlParameter parameter;

        internal FormatCapturingParameter(SqlParameter parameter)       
        {
            this.parameter = parameter;
        }
        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (!string.IsNullOrEmpty(format))                              
            {                                                               
                parameter.SqlDbType = (SqlDbType) Enum.Parse(
                    typeof(SqlDbType), format, true);
            }                                                               
            return parameter.ParameterName;                               
        }
    }
}

The only public part of this is the SqlFormattableString static class with its NewSqlCommand method. Everything else is a hidden implementation detail. For each placeholder in the format string, you create a SqlParameter and a corresponding FormatCapturingParameter. The latter is used to format the parameter name in the SQL as @p0, @p1, and so on, and the value provided to the ToString method is set into the SqlParameter. The type of the parameter is also set if the user specifies it in the format string.

At this point, you need to make up your own mind as to whether this is something you’d like to see in your production codebase. I’d want to implement extra features (such as including the size in the format string; you can’t use the alignment part of a format item, because string.Format handles that itself), but it can certainly be productionized appropriately. But is it just too clever? Are you going to have to walk every new developer on the project through this, saying, “Yes, I know it looks like we have a massive SQL injection vulnerability, but it’s okay, really”?

Regardless of this specific example, you may well be able to find similar situations for which you can use the compiler’s extraction of the data and separation from the text of an interpolated string literal. Always think carefully about whether a solution like this is really providing a benefit or whether it’s just giving you a chance to feel smart.

All of this is useful if you’re targeting .NET 4.6, but what if you’re stuck on an older framework version? Just because you’re using a C# 6 compiler doesn’t mean you’re necessarily targeting a modern version of the framework. Fortunately, the C# compiler doesn’t tie this to a specific framework version; it just needs the right types to be available somehow.

9.3.4. Using FormattableString with older versions of .NET

Just like the attribute for extension methods and caller information attributes, the C# compiler doesn’t have a fixed idea of which assembly should contain the FormattableString and FormattableStringFactory types it relies on. The compiler cares about the namespaces and expects an appropriate static Create method to be present on FormattableStringFactory, but that’s about it. If you want to take advantage of the benefits of FormattableString but you’re stuck targeting an earlier version of the framework, you can implement both types yourself.

Before I show you the code, I should point out that this should be viewed as a last resort. When you eventually upgrade your environment to target .NET 4.6, you should remove these types immediately to avoid compiler warnings. Although you can get away with having your own implementation even if you end up executing in .NET 4.6, I’d try to avoid getting into that situation; in my experience, having the same type in different assemblies can lead to issues that are hard to diagnose.

With all the caveats out of the way, the implementation is simple. Listing 9.10 shows both types. I haven’t included any validation, I’ve made FormattableString a concrete type for brevity, and I’ve made both classes internal, but the compiler doesn’t mind those changes. The reason for making the types internal is to avoid other assemblies taking a dependency on your implementation; whether that’s suitable for your precise situation is hard to predict, but please consider it carefully before making the types public.

Listing 9.10. Implementing FormattableString from scratch
using System.Globalization;

namespace System.Runtime.CompilerServices
{
    internal static class FormattableStringFactory
    {
        internal static FormattableString Create(
            string format, params object[] arguments) =>
            new FormattableString(format, arguments);
    }
}

namespace System
{
    internal class FormattableString : IFormattable
    {
        public string Format { get; }
        private readonly object[] arguments;

        internal FormattableString(string format, object[] arguments)
        {
            Format = format;
            this.arguments = arguments;
        }

        public object GetArgument(int index) => arguments[index];
        public object[] GetArguments() => arguments;
        public int ArgumentCount => arguments.Length;
        public static string Invariant(FormattableString formattable) =>
            formattable?.ToString(CultureInfo.InvariantCulture);
        public string ToString(IFormatProvider formatProvider) =>
            string.Format(formatProvider, Format, arguments);
        public string ToString(
            string ignored, IFormatProvider formatProvider) =>
            ToString(formatProvider);
    }
}

I won’t explain the details of the code, because each individual member is quite simple. The only part that may need a little explanation is in the Invariant method calling formattable?.ToString(CultureInfo.InvariantCulture). The ?. part of this expression is the null conditional operator, which you’ll look at in more detail in section 10.3. Now you know everything you can do with interpolated string literals, but what about what you should do with them?

9.4. Uses, guidelines, and limitations

Like expression-bodied members, interpolated string literals are a safe feature to experiment with. You can adjust your code to meet your own (or team-wide) thresholds. If you change your mind later and want to go back to the old code, doing so is trivial. Unless you start using FormattableString in your APIs, the use of interpolated string literals is a hidden implementation detail. That doesn’t mean it should be used absolutely everywhere, of course. In this section, we’ll discuss where it makes sense to use interpolated string literals, where it doesn’t, and where you might find you can’t even if you want to.

9.4.1. Developers and machines, but maybe not end users

First, the good news: almost anywhere you’re already using string formatting with hardcoded composite format strings or anywhere you’re using plain string concatenation you can use interpolated strings. Most of the time, the code will be more readable afterward.

The hardcoded part is important here. Interpolated string literals aren’t dynamic. The composite format string is there inside your source code; the compiler just mangles it a little to use regular format items. That’s fine when you know the text and format of the desired string beforehand, but it’s not flexible.

One way of categorizing strings is to think about who or what is going to consume them. For the purposes of this section, I’ll consider three consumers:

  • Strings designed for other code to parse
  • Messages for other developers
  • Messages for end users

Let’s look at each kind of string in turn and think about whether interpolated string literals are useful.

Machine-readable strings

Lots of code is built to read other strings. There are machine-readable log formats, URL query parameters, and text-based data formats such as XML, JSON, or YAML. All of these have a set format, and any values should be formatted using the invariant culture. This is a great place to use FormattableString, as you’ve already seen, if you need to perform the formatting yourself. As a reminder, you should typically be taking advantage of an appropriate API for the formatting of machine-readable strings anyway.

Bear in mind that each of these strings might also contain nested strings aimed at humans; each line of a log file may be formatted in a specific way to make it easy to treat as a single record, but the message part of it may be aimed at other developers. You need to keep track of what level of nesting each part of your code is working at.

Messages for other developers

If you look at a large codebase, you’re likely to find that many of your string literals are aimed at other developers, whether they’re colleagues within the same company or developers using an API you’ve released. These are primarily as follows:

  • Tooling strings such as help messages in console applications
  • Diagnostic or progress messages written to logs or the console
  • Exception messages

In my experience, these are typically in English. Although some companies—including Microsoft—go to the trouble of localizing their error messages, most don’t. Localization has a significant cost both in terms of the data translation and the code to use it properly. If you know your audience is at least reasonably comfortable reading English, and particularly if they may want to share the messages on English-oriented sites such as Stack Overflow, it’s usually not worth the effort of localizing these strings.

Whether you go so far as making sure that the values within the text are all formatted in a fixed culture is a different matter. It can definitely help to improve consistency, but I suspect I’m not the only developer who doesn’t pay as much attention to that as I might. I encourage you to use a nonambiguous format for dates, however. The ISO format of yyyy-MM-dd is easy to understand and doesn’t have the “month first or day first?” problem of dd/MM/yyyy or MM/dd/yyyy. As I noted earlier, the culture can affect which numbers are produced because of different calendar systems being in use in different parts of the world. Consider carefully whether you want to use the invariant culture to force the use of the Gregorian calendar. For example, code to throw an exception for an invalid argument might look like this:

throw new ArgumentException(Invariant(
    $"Start date {start:yyyy-MM-dd} should not be earlier than year 2000."))

If you know that all the developers reading these strings are going to be in the same non-English culture, it’s entirely reasonable to write all those messages in that culture instead.

Messages for end users

Finally, almost all applications have at least some text that’s displayed to an end user. As with developers, you need to be aware of the expectations of each user in order to make the right decision for how to present text to them. In some cases, you can be confident that all your end users are happy to use a single culture. This is typically the case if you’re building an application to be used internally within a business or other organization that’s based in one location. Here it’s much more likely that you’ll use whatever that local culture is rather than English, but you don’t need to worry about two users wanting to see the same information presented in different ways.

So far, all these situations have been amenable to interpolated string literals. I’m particularly fond of using them for exception messages. They let me write concise code that still provides useful context to the unfortunate developer poring over logs and trying to work out what’s gone wrong this time.

But interpolated string literals are rarely helpful when you have end users in multiple cultures, and they can hurt your product if you don’t localize. Here, the format strings are likely to be in resource files rather than in your code anyway, so you’re unlikely to even see the possibility of using interpolated string literals. There are occasional exceptions to this, such as when you’re formatting just one snippet of information to put within a specific HTML tag or something similar. In those exceptional cases, an interpolated string literal should be fine, but don’t expect to use them much.

You’ve seen that you can’t use interpolated string literals for resource files. Next, you’ll look at other cases for which the feature simply isn’t designed to help you.

9.4.2. Hard limitations of interpolated string literals

Every feature has its limits, and interpolated string literals are no exception. These limitations sometimes have workarounds, which I’ll show you before generally advising you not to try them in the first place.

No dynamic formatting

You’ve already seen that you can’t change most of the composite format string that makes up the interpolated string literal. Yet one piece feels like it should be expressible dynamically but isn’t: individual format strings. Let’s take one piece of an example from earlier:

Console.WriteLine($"Price: {price,9:C}");

Here, I’ve chosen 9 as the alignment, knowing that the values I’d be formatting would fit nicely into nine characters. But what if you know that sometimes all the values you need to format will be small and other times they may be huge? It’d be nice to make that 9 part dynamic, but there’s no simple way of doing it. The closest you can easily come is to use an interpolated string literal as the input to string.Format or the equivalent Console.WriteLine overload, as in the following example:

int alignment = GetAlignmentFromValues(allTheValues);
Console.WriteLine($"Price: {{0,{alignment}:C}}", price);

The first and last braces are doubled as the escape mechanism in string formats, because you want the result of the interpolated string literal to be a string such as "Price: {0,9}" that’s ready to be formatted using the price variable to fill in the format item. This isn’t code I’d want to either write or read.

No expression reevaluation

The compiler always converts an interpolated string literal into code that immediately evaluates the expressions in the format items and uses them to build either a string or a FormattableString. The evaluation can’t be deferred or repeated. Consider the short example in the following listing. It prints the same value twice, even though the developer may expect it to use deferred execution.

Listing 9.11. Even FormattableString evaluates expressions eagerly
string value = "Before";
FormattableString formattable = $"Current value: {value}";
Console.WriteLine(formattable);                            1

value = "After";
Console.WriteLine(formattable);                            2

  • 1 Prints "Current value: Before"
  • 2 Still prints "Current value: Before"

If you’re desperate, you can work around this. If you change the expression to include a lambda expression that captures value, you can abuse this to evaluate it each time it’s formatted. Although the lambda expression itself is converted into a delegate immediately, the resulting delegate would capture the value variable, not its current value, and you can force the delegate to be evaluated each time you format the FormattableString. This is a sufficiently bad idea that, although I’ve included an example of it in the downloadable samples for the book, I’m not going to sully these pages with it. (It’s still a fun abuse, admittedly.)

No bare colons

Although you can use pretty much any expression computing a value in interpolated string literals, there’s one problem with the conditional ?: operator: it confuses the compiler and indeed the grammar of the C# language. Unless you’re careful, the colon ends up being handled as the separator between the expression and the format string, which leads to a compile-time error. For example, this is invalid:

Console.WriteLine($"Adult? {age >= 18 ? "Yes" : "No"}");

It’s simple to fix by using parentheses around the conditional expression:

Console.WriteLine($"Adult? {(age >= 18 ? "Yes" : "No")}");

I rarely find this to be a problem, partly because I usually try to keep the expressions shorter than this anyway. I’d probably extract the yes/no value into a separate string variable first. This leads us nicely into a discussion about when the choice of whether to use an interpolated string literal really comes down to a matter of taste.

9.4.3. When you can but really shouldn’t

The compiler isn’t going to mind if you abuse interpolated string literals, but your coworkers might. There are two primary reasons not to use them even where you can.

Defer formatting for strings that may not be used

Sometimes you want to pass a format string and the arguments that would be formatted to a method that might use them or might not. For example, if you have a precondition validation method, you might want to pass in the condition to check along with the format and arguments of an exception message to create if (and only if) the condition fails. It’s easy to write code like this:

Preconditions.CheckArgument(
    start.Year < 2000,
    Invariant($"Start date {start:yyyy-MM-dd} should not be earlier than year
     2000."));

Alternatively, you could have a logging framework that’ll log only if the level has been configured appropriately at execution time. For example, you might want to log the size of a request that your server has received:

Logger.Debug("Received request with {0} bytes", request.Length);

You might be tempted to use an interpolated string literal for this by changing the code to the following:

Logger.Debug($"Received request with {request.Length} bytes");

That would be a bad idea; it forces the string to be formatted even if it’s just going to be thrown away, because the formatting will unconditionally be performed before the method is called rather than within the method only if it’s needed. Although string formatting isn’t hugely expensive in terms of performance, you don’t want to be doing it unnecessarily.

You may be wondering whether FormattableString would help here. If the validation or logging library accepted a FormattableString as an input parameter, you could defer the formatting and control the culture used for formatting in a single place. Although that’s true, it’d still involve creating the object each time, which is still an unnecessary cost.

Format for readability

The second reason for not using interpolated string literals is that they can make the code harder to read. Short expressions are absolutely fine and help readability. But when the expression becomes longer, working out which parts of the literal are code and which are text starts to take more time. I find that parentheses are the killer; if you have more than a couple of method or constructor calls in the expression, they end up being confusing. This goes double when the text also includes parentheses.

Here’s a real example from Noda Time. It’s in a test rather than in production code, but I still want the tests to be readable:

private static string FormatMemberDebugName(MemberInfo m) =>
    string.Format("{0}.{1}({2})",
        m.DeclaringType.Name,
        m.Name,
        string.Join(", ", GetParameters(m).Select(p => p.ParameterType)));

That’s not too bad, but imagine putting the three arguments within the string. I’ve done it, and it’s not pretty; you end up with a literal that’s more than 100 characters long. You can’t break it up to use vertical formatting to make each argument stand alone as I have with the preceding layout, so it ends up being noise.

To give one final tongue-in-cheek example of just how bad an idea it can be, remember the code used to start the chapter:

Console.Write("What's your name? ");
string name = Console.ReadLine();
Console.WriteLine("Hello, {0}!", name);

You can put all of this inside a single statement using an interpolated string literal. You may be skeptical; after all, that code consists of three separate statements, and the interpolated string literal can include only expressions. That’s true, but statement-bodied lambda expressions are still expressions. You need to cast the lambda expression to a specific delegate type, and then you need to invoke it to get the result, but it’s all doable. It’s just not pleasant. Here’s one option, which does at least use separate lines for each statement by virtue of a verbatim interpolated string literal, but that’s about all that can be said in its favor:

Console.WriteLine($@"Hello {((Func<string>)(() =>
{
    Console.Write("What's your name? ");
    return Console.ReadLine();
}))()}!");

I thoroughly recommend running: run the code to prove that it works, and then run away from it as fast as you can. While you’re recovering from that, let’s look at the other string-oriented feature of C# 6.

9.5. Accessing identifiers with nameof

The nameof operator is trivial to describe: it takes an expression referring to a member or local variable, and the result is a compile-time constant string with the simple name for that member or variable. It’s that simple. Anytime you hardcode the name of a class, property, or method, you’ll be better off with the nameof operator. Your code will be more robust both now and in the face of changes.

9.5.1. First examples of nameof

In terms of syntax, the nameof operator is like the typeof operator, except that the identifier in the parentheses doesn’t have to be a type. The following listing shows a short example with a few kinds of members.

Listing 9.12. Printing out the names of a class, method, field, and parameter
using System;

class SimpleNameof
{
    private string field;

    static void Main(string[] args)
    {
        Console.WriteLine(nameof(SimpleNameof));
        Console.WriteLine(nameof(Main));
        Console.WriteLine(nameof(args));
        Console.WriteLine(nameof(field));
    }
}

The result is exactly what you’d probably expect:

SimpleNameof
Main
args
field

So far, so good. But, obviously, you could’ve achieved the same result by using string literals. The code would’ve been shorter, too. So why is it better to use nameof? In one word, robustness. If you make a typo in a string literal, there’s nothing to tell you, whereas if you make a typo in a nameof operand, you’ll get a compile-time error.

Note

The compiler still won’t be able to spot the problem if you refer to a different member with a similar name. If you have two members that differ only in case, such as filename and fileName, you can easily refer to the wrong one without the compiler noticing. This is a good reason to avoid such similar names, but it’s always been a bad idea to name things so similarly; even if you don’t confuse the compiler, you can easily confuse a human reader.

Not only will the compiler tell you if you get things wrong, but it knows that your nameof code is associated with the member or variable you’re naming. If you rename it in a refactoring-aware way, your nameof operand will change, too.

For example, consider the following listing. Its purpose is irrelevant, but note that oldName occurs three times: for the parameter declaration, obtaining its name with nameof, and obtaining the value as a simple expression.

Listing 9.13. A simple method using its parameter twice in the body
static void RenameDemo(string oldName)
{
    Console.WriteLine($"{nameof(oldName)} = {oldName}");
}

In Visual Studio, if you place your cursor within any of the three occurrences of oldName and press F2 for the Rename operation, all three will be renamed together, as shown in figure 9.2.

Figure 9.2. Renaming an identifier in Visual Studio

The same approach works for other names (methods, types, and so forth). Basically, nameof is refactoring friendly in a way that hardcoded string literals aren’t. But when should you use it?

9.5.2. Common uses of nameof

I’m not going to claim that the examples here are the only sensible uses of nameof. They’re just the ones I’ve come across most often. They’re mostly places where prior to C# 6, you’d have seen either hardcoded names or, possibly, expression trees being used as a workaround that’s refactoring friendly but complex.

Argument validation

In chapter 8, when I showed the uses of Preconditions.CheckNotNull in Noda Time, that wasn’t the code that’s actually in the library. The real code includes the name of the parameter with the null value, which makes it a lot more useful. The InZone method I showed there looks like this:

public ZonedDateTime InZone(
    DateTimeZone zone,
    ZoneLocalMappingResolver resolver)
{
    Preconditions.CheckNotNull(zone, nameof(zone));
    Preconditions.CheckNotNull(resolver, nameof(resolver));
    return zone.ResolveLocal(this, resolver);
}

Other precondition methods are used in a similar way. This is by far the most common use I find for nameof. If you’re not already validating arguments to your public methods, I strongly advise you to start doing so; nameof makes it easier than ever to perform robust validation with informational messages.

Property change notification for computed properties

As you saw in section 7.2, CallerMemberNameAttribute makes it easy to raise events in INotifyPropertyChanged implementations when the property itself changes. But what if changing the value of one property has an effect on another property? For example, suppose you have a Rectangle class with read/write Height and Width properties and a read-only Area property. It’s useful to be able to raise the event for the Area property and specify the name of the property in a safe way, as shown in the following listing.

Listing 9.14. Using nameof to raise a property change notification
public class Rectangle : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private double width;
    private double height;

    public double Width
    {
        get { return width; }
        set
        {
            if (width == value)                                 1
            {
                return;
            }
            width = value;
            RaisePropertyChanged();                             2
            RaisePropertyChanged(nameof(Area));                 3
        }
    }

    public double Height { ... }                                4

    public double Area => Width * Height;                       5

    private void RaisePropertyChanged(                          6
        [CallerMemberName] string propertyName = null) { ... }

}

  • 1 Avoid raising events when the value isn’t changing.
  • 2 Raises the event for the Width property
  • 3 Raises the event for the Area property
  • 4 Implemented just like Width
  • 5 Computed property
  • 6 Change notification as per section 7.2

Most of this listing is exactly as you’d have written it in C# 5, but the line in bold would’ve had to be RaisePropertyChanged("Area") or RaisePropertyChanged(() => Area). The latter approach would’ve been complex, in terms of the RaisePropertyChanged code, and inefficient, because it builds up an expression tree solely to be inspected for the name. The nameof solution is much cleaner.

Attributes

Sometimes attributes refer to other members to indicate how the members relate to each other. When you want to refer to a type, you can already use typeof to make that relationship, but that doesn’t work for any other kind of member. As a concrete example, NUnit allows tests to be parameterized with values that are extracted from a field, property, or method using the TestCaseSource attribute. The nameof operator allows you to refer to that member in a safe way. The following listing shows yet another example from Noda Time, testing that all the time zones loaded from the Time Zone Database (TZDB, now hosted by IANA) behave appropriately at the start and end of time.

Listing 9.15. Specifying a test case source with nameof
static readonly IEnumerable<DateTimeZone> AllZones =     1
    DateTimeZoneProviders.Tzdb.GetAllZones();            1

[Test]
[TestCaseSource(nameof(AllZones))]                       2
public void AllZonesStartAndEnd(DateTimeZone zone)       3
{
  ...                                                    4
}

  • 1 Field to retrieve all TZDB time zones
  • 2 Refers to the field using nameof
  • 3 Test method called with each time zone in turn
  • 4 Body of test method omitted

The utility here isn’t restricted to testing. It’s applicable wherever attributes indicate a relationship. You could imagine a more sophisticated RaisePropertyChanged method from the preceding section, where the relationship between properties could be specified with attributes instead of within code:

[DerivedProperty(nameof(Area))
public double Width { ... }

The event-raising method could keep a cached data structure indicating that whenever it was notified that the Width property had changed, it should raise a change notification for Area as well.

Similarly, in object-relational mapping technologies such as Entity Framework, it’s reasonably common to have two properties in a class: one for a foreign key and the other to be the entity that key represents. This is shown in the following example:

public class Employee
{
    [ForeignKey(nameof(Employer))]
    public Guid EmployerId { get; set; }
    public Company Employer { get; set; }
}

There are no doubt many other attributes that can take advantage of this approach. Now that you’re aware of it, you may find places in your existing codebase that’ll benefit from nameof. In particular, you should look for code where you need to use reflection with names that you do know at compile time but haven’t previously been able to specify in a clean way. There are still a few little subtleties to cover for the sake of completeness, however.

9.5.3. Tricks and traps when using nameof

You may never need to know any of the details in this section. This content is primarily here just in case you find yourself surprised by the behavior of nameof. In general, it’s a pretty simple feature, but a few aspects might surprise you.

Referring to members of other types

Often, it’s useful to be able to refer to members in one type from within code in another type. Going back to the TestCaseSource attribute, for example, in addition to a name, you can specify a type where NUnit will look for that name. If you have a source of information that’ll be used from multiple tests, it makes sense to put it in a common place. To do this with nameof, you qualify it with the type as well. The result will be the simple name:

[TestCaseSource(typeof(Cultures), nameof(Cultures.AllCultures))]

That is equivalent to the following, except for all the normal benefits of nameof:

[TestCaseSource(typeof(Cultures), "AllCultures")]

You can also use a variable of the relevant type to access a member name, although only for instance members. In reverse, you can use the name of the type for both static and instance members. The following listing shows all the valid permutations.

Listing 9.16. All the valid ways of accessing names of members in other types
class OtherClass
{
    public static int StaticMember => 3;
    public int InstanceMember => 3;
}

class QualifiedNameof
{
    static void Main()
    {
        OtherClass instance = null;
        Console.WriteLine(nameof(instance.InstanceMember));
        Console.WriteLine(nameof(OtherClass.StaticMember));
        Console.WriteLine(nameof(OtherClass.InstanceMember));
    }
}

I prefer to always use the type name where possible; if you use a variable instead, it looks like the value of the variable may matter, but really it’s used only at compile time to determine the type. If you’re using an anonymous type, there’s no type name you could use, so you have to use the variable.

A member still has to be accessible for you to refer to it using nameof; if StaticMember or InstanceMember in listing 9.16 had been private, the code trying to access their names would’ve failed to compile.

Generics

You may be wondering what happens if you try to take the name of a generic type or method and how it has to be specified. In particular, typeof allows both bound and unbound type names to be used; typeof(List<string>) and typeof(List<>) are both valid and give different results.

With nameof, the type argument must be specified but isn’t included in the result. Additionally, there’s no indication of the number of type parameters in the result: nameof(Action<string>) and nameof(Action<string, string>) both have a value of just "Action". This can be irritating, but it removes any question of how the resulting name should represent arrays, anonymous types, further generic types, and so on.

It seems likely to me that the requirement for a type argument to be specified may be removed in the future both to be consistent with typeof and to avoid having to specify a type that makes no difference to the result. But changing the result to include the number of type arguments or the type arguments themselves would be a breaking change, and I don’t envision that happening. In most cases where that matters, using typeof to obtain a Type would be preferable anyway.

You can use a type parameter with a nameof operator, but unlike typeof(T), it’ll always return the name of the type parameter rather than the name of the type argument used for that type parameter at execution time. Here’s a minimal example of that:

static string Method<T>() => nameof(T);       1

  • 1 Always returns "T"

It doesn’t matter how you call the method: Method<Guid>() or Method<Button>() will both return "T".

Using aliases

Usually, using directives providing type or namespace aliases have no effect at execution time. They’re just different ways of referring to the same type or namespace. The nameof operator is one exception to this rule. The output of the following listing is GuidAlias, not Guid.

Listing 9.17. Using an alias in the nameof operator
using System;

using GuidAlias = System.Guid;

class Test
{
    static void Main()
    {
        Console.WriteLine(nameof(GuidAlias));
    }
}
Predefined aliases, arrays and nullable value types

The nameof operator can’t be used with any of the predefined aliases (int, char, long, and so on) or the ? suffix to indicate a nullable value type or array types. Therefore, all the following are invalid:

nameof(float)      1
nameof(Guid?)      2
nameof(String[])   3

  • 1 Predefined alias for System.Single
  • 2 Shorthand for Nullable<Guid>
  • 3 Array

These are a little annoying, but you have to use the CLR type name for the predefined aliases and the Nullable<T> syntax for nullable value types:

nameof(Single)
nameof(Nullable<Guid>)

As noted in the previous section on generics, the name of Nullable<T> will always be Nullable anyway.

The name, the simple name, and only the name

The nameof operator is in some ways a cousin of the mythical infoof operator, which has never been seen outside the room used for C# language design meetings. (See http://mng.bz/6GVe for more information on infoof.) If the team ever manages to catch and tame infoof, it could return references to MethodInfo, EventInfo, PropertyInfo objects, and their friends. Alas, infoof has proved elusive so far, but many of the tricks it uses to evade capture aren’t available to the simpler nameof operator. Trying to take the name of an overloaded method? That’s fine; they all have the same name anyway. Can’t easily resolve whether you’re referring to a property or a type? Again, if they both have the same name, it doesn’t matter which you use. Although infoof would certainly provide benefits above and beyond nameof if it could ever be sensibly designed, the nameof operator is considerably simpler and still addresses many of the same use cases.

One point to note about what’s returned—the simple name or “bit at the end” in less specification-like terminology: it doesn’t matter if you use nameof(Guid) or use nameof(System.Guid) from within a class importing the System namespace. The result will still be only "Guid".

Namespaces

I haven’t given details about all the members that nameof can be used with, because it’s the set you’d expect: basically, all members except finalizers and constructors. But because we normally think about members in terms of types and members within types, you may be surprised that you can take the name of a namespace. Yes, namespaces are also members—of other namespaces.

But given the preceding rule about only the simple name being returned, that isn’t terribly useful. If you use nameof(System.Collections.Generic), I suspect you want the result to be System.Collections.Generic, but in reality, it’s just Generic. I’ve never come across a type where this is useful behavior, but then it’s rarely important to know a namespace as a compile-time constant anyway.

Summary

  • Interpolated string literals allow you to write simpler string-formatting code.
  • You can still use format strings in interpolated string literals to provide more formatting details, but the format string has to be known at compile time.
  • Interpolated verbatim string literals provide a mixture of the features of interpolated string literals and verbatim string literals.
  • The FormattableString type provides access to all the information required to format a string before the formatting takes place.
  • FormattableString is usable out of the box in .NET 4.6 and .NET Standard 1.3, but the compiler will use it if you provide your own implementation in earlier versions.
  • The nameof operator provides refactoring-friendly and typo-safe access to names within your C# code.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset