c#中readonly关键字/表达式成员之间的区别,哪个更好?

mur*_*raj 17 c# c#-6.0

在c#中,readonly成员可以简化为只读自动属性/表达式成员,对于不可变成员,表达式成员比使用readonly关键字更好吗?

使用readonly keywork:

public static readonly string  COMPANY_NAME= "XYZ";
Run Code Online (Sandbox Code Playgroud)

使用表达身体的成员:

public  static  string  COMPANY_NAME => "XYZ";
Run Code Online (Sandbox Code Playgroud)

我遇到过各种论坛和解决方案,建议表达身体的成员用于短手使用,我无法找到它在性能上的差异.

qbi*_*bik 19

让我们深入了解一下编译器对不同类型的字段所做的工作.

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

const string创建一个literal string并将在呼叫站点进行评估:

.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)
Run Code Online (Sandbox Code Playgroud)

static readonly 创建一个在ctor中初始化的字段,并且在使用时仅表示一个字段引用:

.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)
Run Code Online (Sandbox Code Playgroud)

Expression bodied成员生成一个getter,它返回常量值:

.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)
Run Code Online (Sandbox Code Playgroud)

初始化的只读属性会为初始化值生成一个额外的支持字段.

.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)
Run Code Online (Sandbox Code Playgroud)

具有全身的属性获取器有点长:

.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)
Run Code Online (Sandbox Code Playgroud)

从那以后,我们可以通过他们生成的代码(和调用)来订购它们:

  • const string 肯定是最快的一个,但是在更改的情况下,当从其他组件中使用时会引起意外行为(如提到的其他答案)
  • static readonly 紧随其后,只需一次现场访问
  • static string ExpressionBodied => "xxx" 将导致只返回常量的方法调用(getter)
  • static string GetInitialized {get;} = "xxx" 将导致方法调用和字段访问
  • static string GetWithBody { get { return "xxx"; } } 会导致一个返回常量的方法调用,但是看起来还有额外的内存分配

在实践中,性能差异可能是不可观察的.正如所指出的,IL代码可以通过JIT进一步优化,因此您可以最终获得相同的性能.不过,我更喜欢选择1.或2.

  • 请记住,您在IL中看到的内容可能会产生误导,因为现代JIT可以解决大部分繁重问题.你在JITting之后得到的结果可能非常模仿你在IL中观察到的东西.特别是返回常量字符串的getter可以在与没有任何getter的常量字符串相同的代码中进行JIT.因此,按"代码量"对它们进行评级并不是很有用. (4认同)

Pat*_*man 10

首先,你应该使用conststring常量,而不是readonly.您应该仅将后者用于需要构造函数调用来构造它们的对象.

这里有一个注意事项,正如注释中所述,你应该知道常量甚至会跨程序集进行优化(因此你的库常量也可以在引用的库的编译时被评估为常量).这意味着,通过次要版本更新,您可能最终在程序集中使用另一个常量值,而不是在库中.在这种情况下,你应该继续使用static readonly.

其次,static readonly字段和static属性之间存在巨大差异.该static属性将得到每一个你怎么称呼它时进行评估.在static readonly略微优化,因为它不只是做一个单一的任务.

  • "可以优化"是不正确的.它必须*进行优化,它在C#规范中也是如此.此外,不可能将常量引用为程序集元数据中的标记,因此实际上没有任何替代方法.因此,如果值稍后可以更改,则使用readonly,如果它确实是常量,则仅使用const.另请参阅此/sf/answers/3921711/. (2认同)

Dam*_*ver 7

在这种情况下,整体结果将显得相同,但意识到它们是完全不同的.

第一个定义了一个readonly字段.=运行一次右侧的初始化表达式,该字段始终返回该值.

第二个定义了一个get-only属性.每次访问,=>都会评估右侧的表达式.

在这种情况下,表达式是确定性的并生成不​​可变对象.如果这些都不是真的,那么它们之间的差异将是可观察的(通过第二次返回不同的结果或者能够修改第一个的内容)