Why you should use static read-only fields over constants when dealing with strings

It’s really wonderful when you learn something new once in a while. As I was discussing with another .NET developer about the importance of knowing when to use constants and fields, he corrected me when I mistakenly told him that String.Empty was implemented as a constant.  In fact, if you look inside the String class, you’ll actually see that String.Empty is not implemented as a constant, but as a field (a static read-only field to be precise).

As I was thinking about the reason behind this, something hit me: I should be conscious of my decision whether I should use a constant or a field when dealing with strings.  The reason is simple: avoid unnecessary performance hits.  Take note that the only valid reference type for a constant is either string or null because you cannot instantiate a reference type in compile-time, which is when constants are evaluated.

I made a simple test to verify my little theory, since I couldn’t find a good answer for it on the Web. The test consist of a class that declares both a constant string and a static read-only string field. In its entry method, I call various methods which iterate a certain number of times (10, 100, 1000, 10 000 and 100 000) and use either the constant or the static read-only field respectively.  Here’s a sample code I used for this test:

class Program
{
  private const string Constant = "Constant";
  private static readonly string field = "field";

  private static void Main(string[] args)
  {
    // 10 iterations
    DoConstant10();
    DoField10();

    // 100 iterations
    DoConstant100();
    DoField100();

    // 1000 iterations
    DoConstant1000();
    DoField1000();

    // 10000 iterations
    DoConstant10000();
    DoField10000();

    // 100000 iterations
    DoConstant100000();
    DoField100000();
  }

  private static void DoField100000()
  {
    string s = string.Empty;
    for (int i = 0; i < 100000; i++)
	  s += field;
  }

  private static void DoConstant100000()
  {
    string s = string.Empty;
    for (int i = 0; i < 100000; i++)
	  s += Constant;
  }

  // And so on...
}

Next, I profiled the application with JetBrains DotTrace 3.1 (using the standard configuration options) and got the following results:

image

We clearly see that for the same iterations, using a string as field rather than a constant has almost a 2:1 performance improvement ratio over the latter.  Now, the title of the post is “Why you should use…” and not “Why you must use…” because there are some scenarios when using a string constant is better suited, such as when the constant is being used as a case in a switch statement (you cannot use a static readonly field as a case in a switch statement because its value will not be known until runtime).

Another thing you should be conscious of is relying on your experience rather than a tool’s recommendation. For example, when you declare a static read-only string variable, ReSharper will recommend you to transform it as a constant.  You shouldn’t always obey the tool unless you know exactly the tradeoffs and have considered the impact and risks associated with the decision.

image Be a good judge over your codebase. Analyze the evidence and study the tradeoffs. There’s nothing like a good profiler to uncover the truths behind your code.

This post has been viewed: 1757 times. kick it on DotNetKicks.com

 

Similar posts you might be interested in reading:

5 Comments

  1. Przemyslaw:

    I do not know framework internals so well, but does the fact that you actually test both cases using different strings matter? “Constant” and “field” have different length. Doesn’t it impact concatenation performance?
    Care to test again with the same string value? e.g.
    private const string Constant = “string”;
    private static readonly string field = “string”;

  2. Greg Young:

    Previous poster is correct. The issue is the concat of different length strings.

    const and static readonly are synonyms for each other in the CLR.

    Cheers,

    Greg

  3. Mike Duray:

    I agree with the two previous posters, but seeing this perked my interest since I wasn’t quite sure what was going on at the IL level.

    Here’s my test program:

    class Program
    {
    private const string ConstStr = “Same Value”;
    private static readonly string ReadOnlyStr = “Same Value”;

    static void Main(string[] args)
    {
    for (int iteration = 1; iteration <= 5; iteration++)
    {
    int start = Environment.TickCount;
    DoReadOnlyField(Convert.ToInt32(Math.Pow(10, iteration)));
    Console.WriteLine(”DoReadOnlyField: Iteration {0}: Ticks elapsed: {1}”, iteration, Environment.TickCount – start);

    start = Environment.TickCount;
    DoConstField(Convert.ToInt32(Math.Pow(10, iteration)));
    Console.WriteLine(”DoConstField: Iteration {0}: Ticks elapsed: {1}”, iteration, Environment.TickCount – start);

    Console.WriteLine();
    }

    Console.ReadKey();
    }

    private static void DoReadOnlyField(int iterations)
    {
    string s = string.Empty;
    for (int i = 0; i < iterations; i++)
    {
    s += ReadOnlyStr;
    }
    }

    private static void DoConstField(int iterations)
    {
    string s = string.Empty;
    for (int i = 0; i < iterations; i++)
    {
    s += ConstStr;
    }
    }
    }

    The results? Pretty much inconclusive. Not to nitpick but there’s also the possibility that the GC could interfere with the results. But when digging up the IL I did note the following difference: In DoConstField, there is an instruction:
    IL_000d: ldstr “Same Value”

    This differs from the comparable instruction in DoReadOnlyField:
    IL_000d: ldsfld string StaticVsConst.Program::ReadOnlyStr

    I don’t know if this is enough to say that a const field could truly be thought of as an equivalent of a c++ #define (where the variable is replaced with the value at compile time) since the .net const field would still need to be available for reflection. Or at least one would think.

  4. Momo:

    Dude please remove or correct this post as it is the third result in google and although it has a point about the String.Empty your main argument is misleading. For a year or so I was fooled too and I don’t like the fact that i wrote a lot of static readonly strings thinking that I am actually being smart.

  5. Jadawin:

    > Care to test again with the same string value? e.g.
    > private const string Constant = “string”;
    > private static readonly string field = “string”;

    I did this:
    100k field took 77,836 ms, 100k constant took 74,564 ms.
    10k field took 445 ms, 10k constant took 440 ms.

    So, I guess I’ll use constants. :)

Leave a comment

Powered by WP Hashcash