Thursday, April 26, 2007

.NET Literals Are Implicit Instances.

In .NET literals are really just instances without a name.

Many developers will add extra fields to their code when they simply want to perform an operation on a simple string or numeric literal. This is unnecessary because .NET exposes the same methods from a literal as from an explicit instance field. In .NET literals are implicitly treated as instances.

Arguments about factoring and embedding strings and values in your code aside, this is more about what you *can* do than what the proper way to manage literals is.

When working with a string or numeric literal you don't need to create a new instance field to perform operations on the value. You can call the methods directly because the literal *is* an instance. You can call any non-static method that is exposed by the implicit type of the literal.

For example, you need to compare an unknown value against a constant value that doesn't change. The method could be written as:

/// <summary>

/// Contrived Literals Example 1

/// </summary>

/// <param name="value"></param>

/// <returns></returns>

public bool IsContrivedExample1( int value )

{

    int importantNumber = 1;

    int comparison = importantNumber.CompareTo( value );

    bool result;

    if ( comparison == 0 )

    {

        result = true;

    }

    else

    {

        result = false;

    }

    return result;

}



Some might look at that and not see a problem, and logically, there is no real problem. If it's a method that doesn't get called very often the additional field creation and assignments operations don't matter. The fields are well-named and their roles are clear. But this is an extremely contrived example. Most applications code-bases are generally made up of hundred's or thousands of lines of code, thereby making brevity of code your friend.

Here's an example of using the numeric literal directly to perform the same operations.

/// <summary>

/// Contrived Literals Example 2

/// </summary>

/// <param name="value"></param>

/// <returns></returns>

public bool IsContrivedExample2( int value )

{

    return ( 1.CompareTo( value ) == 0 );

}



We were able to reduce the code by 7 lines (not including braces). Not much in this case, but let's say you have 700 lines of methods like this, you drop about 600 lines of code! Not to mention 3 field creation and 3 assignment operations per method.

Now let's take a look at the resultant IL to see what .NET thinks of the differences in code structure.


.method public hidebysig instance bool IsContrivedExample1(int32 'value') cil managed
{
// Code size 41 (0x29)
.maxstack 2
.locals init ([0] int32 importantNumber,
[1] int32 comparison,
[2] bool result,
[3] bool CS$1$0000,
[4] bool CS$4$0001)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: ldloca.s importantNumber
IL_0005: ldarg.1
IL_0006: call instance int32 [mscorlib]System.Int32::CompareTo(int32)
IL_000b: stloc.1
IL_000c: ldloc.1
IL_000d: ldc.i4.0
IL_000e: ceq
IL_0010: ldc.i4.0
IL_0011: ceq
IL_0013: stloc.s CS$4$0001
IL_0015: ldloc.s CS$4$0001
IL_0017: brtrue.s IL_001f
IL_0019: nop
IL_001a: ldc.i4.1
IL_001b: stloc.2
IL_001c: nop
IL_001d: br.s IL_0023
IL_001f: nop
IL_0020: ldc.i4.0
IL_0021: stloc.2
IL_0022: nop
IL_0023: ldloc.2
IL_0024: stloc.3
IL_0025: br.s IL_0027
IL_0027: ldloc.3
IL_0028: ret
} // end of method LiteralsTest::IsContrivedExample1



.method public hidebysig instance bool IsContrivedExample2(int32 'value') cil managed
{
// Code size 19 (0x13)
.maxstack 2
.locals init ([0] bool CS$1$0000,
[1] int32 CS$0$0001)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.1
IL_0003: ldloca.s CS$0$0001
IL_0005: ldarg.1
IL_0006: call instance int32 [mscorlib]System.Int32::CompareTo(int32)
IL_000b: ldc.i4.0
IL_000c: ceq
IL_000e: stloc.0
IL_000f: br.s IL_0011
IL_0011: ldloc.0
IL_0012: ret
} // end of method LiteralsTest::IsContrivedExample2


The second example definitely has less overhead. There are less fields being created and loaded and less logical operations.

Let's see what the IL looks like if we replace the integer literal with a constant integer that is declared in the containing class.


.method public hidebysig instance bool IsContrivedExample3(int32 'value') cil managed
{
// Code size 19 (0x13)
.maxstack 2
.locals init ([0] bool CS$1$0000,
[1] int32 CS$0$0001)
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.1
IL_0003: ldloca.s CS$0$0001
IL_0005: ldarg.1
IL_0006: call instance int32 [mscorlib]System.Int32::CompareTo(int32)
IL_000b: ldc.i4.0
IL_000c: ceq
IL_000e: stloc.0
IL_000f: br.s IL_0011
IL_0011: ldloc.0
IL_0012: ret
} // end of method LiteralsTest::IsContrivedExample3


Hmm, aside from the method name, the IL is exactly identical. It appears that there is no penalty for embedding a literal in our method nor is there a benefit for using a constant.

I used to be a steadfast believer in placing *all* my literals in constant fields. Unless the field needs to be reused elsewhere, keeping literals with the methods that utilize them, along with judicious commenting, will keep my code-base cleaner and easier to manage.

Using literals in your code is up to your discretion and coding standards. I hope that you were able to use this article to expand your .NET coding repertoire.

Below is a class containing several examples of calling instance methods directly against string and integer literals.

public class LiteralsTest

{

    private const int ContrivedValue = 1;

 

    /// <summary>

    /// Contrived Literals Example 1

    /// </summary>

    /// <param name="value"></param>

    /// <returns></returns>

    public bool IsContrivedExample1( int value )

    {

        int importantNumber = 1;

        int comparison = importantNumber.CompareTo( value );

        bool result;

        if ( comparison == 0 )

        {

            result = true;

        }

        else

        {

            result = false;

        }

        return result;

    }

 

    /// <summary>

    /// Contrived Literals Example 2

    /// </summary>

    /// <param name="value"></param>

    /// <returns></returns>

    public bool IsContrivedExample2( int value )

    {

        return ( 1.CompareTo( value ) == 0 );

    }

 

    /// <summary>

    /// Contrived Literals Example 3

    /// </summary>

    /// <param name="value"></param>

    /// <returns></returns>

    public bool IsContrivedExample3( int value )

    {

        return ( ContrivedValue.CompareTo( value ) == 0 );

    }

 

    /// <summary>

    /// Tests the string literals.

    /// </summary>

    public void TestStringLiterals()

    {

        System.Console.Out.WriteLine( "Testing String Literals" );

        System.Console.Out.WriteLine( "fizz".CompareTo( "buzz" ) );

        System.Console.Out.WriteLine( "fizz".CompareTo( "fizz" ) );

        System.Console.Out.WriteLine( "buzz".CompareTo( "fizz" ) );

        System.Console.Out.WriteLine( "TypeCode: {0}", "fizz".GetTypeCode() );

        System.Console.Out.WriteLine( "Type: {0}", "fizz".GetType() );

        System.Console.Out.WriteLine( "HashCode: {0}", "fizz".GetHashCode() );

        System.Console.Out.WriteLine( "fizz".Equals( "buzz" ) );

        System.Console.Out.WriteLine( "fizz".Equals( "fizz" ) );

        System.Console.Out.WriteLine( "buzz".Equals( "fizz" ) );

        System.Console.Out.WriteLine( "fizz".Length );

        System.Console.Out.WriteLine( "Character at index 0: {0}", "fizz"[ 0 ] ); //wha, wha, what? ;-)

        System.Console.Out.WriteLine( "Clone(): {0}", "fizz".Clone() );

        System.Console.Out.WriteLine( "Contains(\"iz\"): {0}", "fizz".Contains( "iz" ) );

        // We are replacing the "working" variable with the string literal value.

        // It's a bit of a contrived example, but the method works.

        char[] working = new char[] {'b', 'u', 'z', 'z'};

        "fizz".CopyTo( 0, working, 0, 4 );

        System.Console.Out.WriteLine( working );

    }

 

    /// <summary>

    /// Tests the integer literals.

    /// </summary>

    public void TestIntegerLiterals()

    {

        System.Console.Out.WriteLine( "Testing Integer Literals" );

        System.Console.Out.WriteLine( 1.CompareTo( 0 ) );

        System.Console.Out.WriteLine( 1.CompareTo( 1 ) );

        System.Console.Out.WriteLine( 0.CompareTo( 1 ) );

        // Cannot implicitly convert type 'int' to 'string'.

        string x = 1.ToString();

        System.Console.Out.WriteLine( "ToString(): {0}", x );

        System.Console.Out.WriteLine( "TypeCode: {0}", 1.GetTypeCode() );

        System.Console.Out.WriteLine( "Type: {0}", 1.GetType() );

        System.Console.Out.WriteLine( "HashCode: {0}", 1.GetHashCode() );

        System.Console.Out.WriteLine( 1.Equals( 0 ) );

        System.Console.Out.WriteLine( 1.Equals( 1 ) );

        System.Console.Out.WriteLine( 0.Equals( 1 ) );

    }

 

    /// <summary>

    /// Runs the tests.

    /// </summary>

    public void RunTests()

    {

        TestStringLiterals();

        System.Console.Out.WriteLine();

        TestIntegerLiterals();

    }

}

Submit this story to DotNetKicks

0comments: