c#?中只读关键字/表达式主体成员之间的差异,哪一个更好?

c#?中只读关键字/表达式主体成员之间的差异,哪一个更好?,c#,c#-6.0,C#,C# 6.0,在c#中,只读成员可以减少为只读自动属性/表达式体成员,对于不可变成员,表达式体成员是否比使用只读关键字更好 使用只读密钥工作: public static readonly string COMPANY_NAME= "XYZ"; 使用表达式体成员: public static string COMPANY_NAME => "XYZ"; 我遇到过各种论坛和解决方案,建议使用expression Body会员进行速记,但我找不到它们在性能上有什么不同。在这种情况下,总体结果看起来

在c#中,只读成员可以减少为只读自动属性/表达式体成员,对于不可变成员,表达式体成员是否比使用只读关键字更好

使用只读密钥工作:

public static readonly string  COMPANY_NAME= "XYZ";
使用表达式体成员:

public  static  string  COMPANY_NAME => "XYZ";
我遇到过各种论坛和解决方案,建议使用expression Body会员进行速记,但我找不到它们在性能上有什么不同。

在这种情况下,总体结果看起来是一样的,但我意识到它们是完全不同的

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

第二个定义了一个仅获取属性。
=>
右侧的表达式将在每次访问时进行求值


在这种情况下,表达式是确定性的,并生成不可变的对象。如果两者都不正确,则可以观察到它们之间的差异(通过第二个返回不同的结果或通过能够修改第一个结果的内容)

首先,您应该使用
常量
字符串
常量,而不是
只读
。您应该仅对需要构造函数调用来构造它们的对象使用后者

不过,正如注释中所述,还需要注意的是,即使在程序集之间,常量也会得到优化(因此,您的库常量也可以在编译时由引用的库作为常量进行计算)。这意味着,如果进行较小的版本更新,最终可能会在程序集中得到另一个常量值,而不是在库中。在这种情况下,您应该继续使用
静态只读


其次,
静态只读
字段和
静态
属性之间存在巨大差异。
static
属性将在每次调用时得到评估。
静态只读
略微优化,因为它只执行一个赋值。

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

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);
    }
}
常量字符串
创建一个
文本字符串
,并将在调用站点进行评估:

.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)
静态只读
创建一个字段,该字段在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)
表达式体成员生成一个getter,该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)
带有初始化的只读属性为初始化值生成一个额外的支持字段

.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)
由此,我们可以根据它们生成的代码(和调用)数量对它们进行排序:

  • const string
    无疑是最快的一个,但是当从其他assembile使用时(如其他答案所述),在发生更改时可能会导致意外行为
  • 静态只读
    紧随其后,只有一个字段访问权限
  • 静态字符串ExpressionBodied=>“xxx”
    将导致一个方法调用(getter),该调用只返回一个常量
  • 静态字符串GetInitialized{get;}=“xxx”
    将导致方法调用和字段访问
  • 静态字符串GetWithBody{get{return“xxx”;}}
    将导致一个方法调用,该调用返回一个常量,但似乎需要额外的内存分配
实际上,性能差异可能无法观察到。正如所指出的,IL代码可以通过JIT进行进一步优化,因此最终可以有效地获得相同的性能。
尽管如此,我还是倾向于选择1。或者2.

对于字符串,它相当简单:使用
const
。在所有其他情况下:这取决于你的偏好,因此是基于观点的。尤其是术语“betetr”可以表示许多不同的含义。你这是什么意思?嗨。因为这里的评论和答案表明你实际上是在比较苹果和橙子,所以对这个问题的任何回答,如果试图告诉你一个比另一个好,都将基于观点而不是事实,因为两者都不是万能的。如果你担心读像
COMPANY\u NAME
这样的字段的性能,你或许应该停止担心性能,先读一读,尤其是“第三部分”。在常量的属性上选择字段是有原因的,性能可能是其中之一,但在这种情况下,性能永远不应该是您做出选择的主要原因,因为语义上存在明显差异。请记住,您在IL中看到的内容可能会非常误导,正如现代JIT所做的那样,大部分工作都是繁重的。你在JITting之后得到的结果可能非常模糊地类似于你在IL中观察到的结果。特别是,返回常量字符串的getter可以在与不带任何getter的常量字符串相同的代码中进行JIT。因此,按“代码量”对它们进行评级并不是很有用。我相信GetWithBody属性中的额外成本来自调试编译。在我看来,这就像是关闭了编译器优化。@AndrewSavinykh说得好,我在答案中添加了这样的注释。@RichardRobertson我怀疑是这样,但在
发行版
配置中,它看起来是一样的(虽然我没有进一步调查,也许我遗漏了什么)@qbik哦,这很有趣。但是Andrew关于抖动的看法仍然是正确的,仍然将其转变为几乎直接的常数读取。
.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)