In C# programming, magic strings often get a bad reputation. They are seen as a code smell, something to be avoided. The advent of nameof(x) in C# gave us a great alternative to magic strings in many cases, perhaps most famously for argument exceptions, like this:

if (somearg is null) throw new ArgumentNullException(nameof(someof));

This is a great use for nameof because if somearg is renamed, the string value passed to the exception’s constructor will change too, which is definitely what you want.

There are times however when a string may match the name of an identifier, but nameof should not be used. Surprised? Read on.

Some constants that you may define have string values that must not change. Consider this example of an HTTP header:

private const string Authorization = nameof(Authorization); // BAD

Here’s the problem: a rename refactoring command in your IDE (that might be activated wherever the Authorization constant is referenced) won’t just change the member name; it will change its value as well. And in the case of a protocol magic string, that will break your compliance with the protocol.

For example, suppose you had a reference to this constant:

request.AddHeader(Authorization, "Bearer abc");

And further suppose you decided that AuthorizationHeader would be a nicer name for this constant, so you triggered a rename refactoring on Authorization right there on the reference to obtain this:

request.AddHeader(AuthorizationHeader, "Bearer abc");

Unbeknownst to you or the C# compiler, you’ve now changed the value of the header itself:

private const string AuthorizationHeader = nameof(AuthorizationHeader); // OOPS

That will break your HTTP code, of course.

The following syntax is therefore more resilient:

private const string Authorization = "Authorization"; // GOOD, resilient code

A rename on the constant will only change the member name and leave the string literal as it should be.

What are persisted strings?

Let’s review which strings that are better left as string literals and avoid nameof(x).

If the string value is persisted in any way (written to disk, the network, or saved in another assembly) aside from the constant declaration itself, it should remain as a string literal because changes to these values would likely break functionality if they were to be changed in the program while the persisted form or other programs retained the original value. Here are a few examples of when to avoid nameof:

  • Protocols: As in the example above, communication protocols are between 2 or more programs, typically running on different processes or machines. They each have a copy of the constant that they are going to read or write. If you change your copy, you haven’t changed theirs, and communication will fail.
  • Database schemas: Databases persist column and table names and expect exact matches for subsequent access. A renamed C# identifier doesn’t rename the database entity.
  • Embeddable strings: The C# compiler will sometimes embed a copy of a constant string in a referencing assembly. Attribute values are a classic case for this. If your assembly declares
    public const string MyConstant = nameof(MyConstant);
    You might feel safe because if you change the value of the constant, at runtime all assemblies that reuse your constant will get the new value. But think again, because this will copy the value of the string into the assembly that uses the attribute:
    [SomeAttribute(YourAssembly.MyConstant)]
    class TheirClass { }
    To follow assembly metadata rules, this becomes in the metadata:
    [SomeAttribute("MyConstant")]
    So that if you later change your constant to:
    public const string MyCoolConstant = nameof(MyCoolConstant);
    The referencing assembly will still have "MyConstant" in the attribute and it will not match.

Conclusion

While magic strings can be a source of errors if used improperly, they can also provide significant benefits in certain situations. When used correctly, they can make your code more readable, maintainable, and robust. The nameof() operator in C# provides additional safety and convenience, particularly when you want the string to track the name of the type or member. However, in cases where the string is defined by a specification and should not change, using nameof() can be hazardous. As with any tool, the key is to understand when and how to use it effectively.

Leave a Reply

Your email address will not be published. Required fields are marked *