Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between readonly keyword/Expression-bodied members in c#?, which is better?

Tags:

In c# readonly members can be reduced to readonly auto properties/expression-bodied members for immutable members is expression-bodied members are better then using readonly keywords?

Using readonly keywork:

public static readonly string  COMPANY_NAME= "XYZ";

Using Expression-bodied Members:

public  static  string  COMPANY_NAME => "XYZ";

I have come across various forums and solution that suggest expression bodied members for short hand use, and i could not find how it differs in performance.

like image 757
murugesan ponraj Avatar asked Aug 10 '18 06:08

murugesan ponraj


People also ask

What are expression-bodied members?

Expression-bodied members provide a minimal and concise syntax to define properties and methods. It helps to eliminate boilerplate code and helps writing code that is more readable. The expression-bodied syntax can be used when a member's body consists only of one expression.

What is an expression-bodied method?

An expression-bodied method consists of a single expression that returns a value whose type matches the method's return type, or, for methods that return void , that performs some operation.

Which is correct syntax for expression-bodied function?

The Syntax of expression body definition is, member => expression; where expression should be a valid expression and member can be any from above list of type members.

Can C# expression-bodied method contain multiple expression?

Yes, you can.


2 Answers

Let's dive deep and see what compiler does for different types of fields.

class Program
{
    public const string ConstString = "mesa const";
    public static readonly string ReadonlyStatic = "mesa readonly";
    public static string ExpressionBodied => "mesa expression";
    public static string GetInitialized {get;} =  "mesa get";
    public static string GetWithBody { get { return "mesa get"; } } 

    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");

        System.Console.WriteLine("readonly:" + ReadonlyStatic);
        System.Console.WriteLine("const:" + ConstString);
        System.Console.WriteLine("expression bodied:" + ExpressionBodied);
        System.Console.WriteLine("get initialized:" + GetInitialized);
        System.Console.WriteLine("get with body:" + GetWithBody);
    }
}

const string creates a literal string and will be evaluated at call site:

.field public static literal string ConstString = "mesa const"

// call site:
IL_0021: ldstr        "const:mesa const"
IL_0026: call         void [System.Console]System.Console::WriteLine(string)

static readonly creates a field that is initialized in ctor and means only one field reference when used:

.field public static initonly string ReadonlyStatic

// call site:
IL_000c: ldstr        "readonly:"
IL_0011: ldsfld       string readonly_props.Program::ReadonlyStatic
IL_0016: call         string [System.Runtime]System.String::Concat(string, string)
IL_001b: call         void [System.Console]System.Console::WriteLine(string)

Expression bodied member generates a getter, which returns constant value:

.method public hidebysig static specialname string 
get_ExpressionBodied() cil managed 
{
  .maxstack 8

  // [9 50 - 9 67]
  IL_0000: ldstr        "mesa expression"
  IL_0005: ret          
} // end of method Program::get_ExpressionBodied

// call site:
IL_002c: ldstr        "expression bodied:"
IL_0031: call         string readonly_props.Program::get_ExpressionBodied()
IL_0036: call         string [System.Runtime]System.String::Concat(string, string)
IL_003b: call         void [System.Console]System.Console::WriteLine(string)

Readonly property with initialization generates an additional backing field for the initializing value.

.field private static initonly string '<GetInitialized>k__BackingField'    
.method public hidebysig static specialname string 
  get_GetInitialized() cil managed 
{
  .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() 
  = (01 00 00 00 )
  .maxstack 8
  // [10 46 - 10 50]
  IL_0000: ldsfld       string readonly_props.Program::'<GetInitialized>k__BackingField'
  IL_0005: ret          
} // end of method Program::get_GetInitialized

// call site:
IL_0041: ldstr        "get initialized:"
IL_0046: call         string readonly_props.Program::get_GetInitialized()
IL_004b: call         string [System.Runtime]System.String::Concat(string, string)
IL_0050: call         void [System.Console]System.Console::WriteLine(string)

A Property getter with full body is a bit longer:

.method public hidebysig static specialname string 
  get_GetWithBody() cil managed 
{
  .maxstack 1
  .locals init (
    [0] string V_0
  )

  // [11 48 - 11 49]
  IL_0000: nop          

  // [11 50 - 11 68]
  IL_0001: ldstr        "mesa get"
  IL_0006: stloc.0      // V_0
  IL_0007: br.s         IL_0009

  // [11 69 - 11 70]
  IL_0009: ldloc.0      // V_0
  IL_000a: ret          

} // end of method Program::get_GetWithBody

// call site:
IL_0056: ldstr        "get with body:"
IL_005b: call         string readonly_props.Program::get_GetWithBody()
IL_0060: call         string [System.Runtime]System.String::Concat(string, string)
IL_0065: call         void [System.Console]System.Console::WriteLine(string)

From that, we can order them by amount of code (and calls) they generate:

  • const string is definitely the fastest one, but can cause unexpected behavior in case of change, when used from other assembiles (as other answers mentioned)
  • static readonly comes right behind, with one field access
  • static string ExpressionBodied => "xxx" will cause a method call (getter) that simply returns a constant
  • static string GetInitialized {get;} = "xxx" will result in a method call and a field access
  • static string GetWithBody { get { return "xxx"; } } will cause a method call that returns a constant, but wih additional memory allocation, it would seem

In practice, the performance differences will probably be unobservable. As pointed out, IL code can be further optimized by JIT, so you can end up with effectively the same performance. Nevertheless, I prefer to go with option 1. or 2.

like image 63
qbik Avatar answered Oct 04 '22 01:10

qbik


First, you should use const with string constants, not readonly. You should use the latter only for objects requiring a constructor call to construct them.

There is a side note to this though, as stated in comments, you should be aware that constants will be optimized even across assemblies (so your library constants can also be evaluated as constants on compile time by the referenced library). That would mean that with minor version updates, you could end up with another constant value in the assembly than in your library. In such cases you should keep using static readonly.

Second, there is a huge difference between static readonly fields and static properties. The static properties will get evaluated each and every time you call it. The static readonly is slightly optimized, since it does only do a single assignment.

like image 31
Patrick Hofman Avatar answered Oct 04 '22 01:10

Patrick Hofman