c#中只读keyword/Expression-bodied成员之间的区别?哪个更好?
Difference between readonly keyword/Expression-bodied members in c#?, which is better?
在 c# 中,readonly 成员可以减少为 readonly auto properties/expression-bodied 不可变成员的成员是表达式体成员比使用 readonly 关键字更好吗?
使用只读关键字:
public static readonly string COMPANY_NAME= "XYZ";
使用表达式主体成员:
public static string COMPANY_NAME => "XYZ";
我遇到过各种论坛和解决方案,它们建议使用 expression bodied 成员进行速记,但我找不到它在性能上有何不同。
在这种情况下,总体结果看起来是一样的,但实际上它们是完全不同的。
第一个定义了一个readonly
字段。 =
右侧的初始化表达式运行一次,字段始终 returns 该值。
第二个定义了一个 get
-only 属性。 =>
右侧的表达式将在每次 被访问时计算 。
在这种情况下,您的表达式是确定性的并产生不可变对象。如果这些都不是真的,那么它们之间的差异将是可见的(通过第二个返回不同的结果或能够修改第一个的内容)
首先,您应该将 const
与 string
常量一起使用,而不是 readonly
。您应该仅将后者用于需要调用构造函数来构造它们的对象。
虽然有一个旁注,如评论中所述,您应该知道常量甚至会跨程序集进行优化(因此您的库常量也可以在编译时被引用的库评估为常量) .这意味着随着次要版本的更新,您最终可能会在程序集中得到另一个常量值,而不是在您的库中。在这种情况下,您应该继续使用 static readonly
.
其次,static readonly
字段和 static
属性之间存在巨大差异。 static
属性将在您每次调用它时进行评估。 static readonly
稍作优化,因为它只做一次赋值。
让我们深入了解一下编译器对不同类型的字段做了什么。
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
创建一个 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)
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)
Expression bodied成员生成一个getter,其中returns常量值:
.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 属性 with initialization 为初始化值生成一个额外的支持字段。
.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)
一个属性getter全身长了一点:
.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)
据此,我们可以按它们生成的代码(和调用)数量对它们进行排序:
const string
绝对是最快的,但是当从其他组合使用时(如其他答案所述)
static readonly
紧随其后,有一个字段访问权限
static string ExpressionBodied => "xxx"
将导致方法调用 (getter) 只是 returns 一个常量
static string GetInitialized {get;} = "xxx"
将导致方法调用和字段访问
static string GetWithBody { get { return "xxx"; } }
将导致一个方法调用 returns 一个常量,但是如果有额外的内存分配,看起来
在实践中,性能差异可能无法观察到。正如所指出的,IL 代码可以通过 JIT 进一步优化,因此您最终可以获得有效的相同性能。
不过,我更喜欢选择选项 1. 或 2.
在 c# 中,readonly 成员可以减少为 readonly auto properties/expression-bodied 不可变成员的成员是表达式体成员比使用 readonly 关键字更好吗?
使用只读关键字:
public static readonly string COMPANY_NAME= "XYZ";
使用表达式主体成员:
public static string COMPANY_NAME => "XYZ";
我遇到过各种论坛和解决方案,它们建议使用 expression bodied 成员进行速记,但我找不到它在性能上有何不同。
在这种情况下,总体结果看起来是一样的,但实际上它们是完全不同的。
第一个定义了一个readonly
字段。 =
右侧的初始化表达式运行一次,字段始终 returns 该值。
第二个定义了一个 get
-only 属性。 =>
右侧的表达式将在每次 被访问时计算 。
在这种情况下,您的表达式是确定性的并产生不可变对象。如果这些都不是真的,那么它们之间的差异将是可见的(通过第二个返回不同的结果或能够修改第一个的内容)
首先,您应该将 const
与 string
常量一起使用,而不是 readonly
。您应该仅将后者用于需要调用构造函数来构造它们的对象。
虽然有一个旁注,如评论中所述,您应该知道常量甚至会跨程序集进行优化(因此您的库常量也可以在编译时被引用的库评估为常量) .这意味着随着次要版本的更新,您最终可能会在程序集中得到另一个常量值,而不是在您的库中。在这种情况下,您应该继续使用 static readonly
.
其次,static readonly
字段和 static
属性之间存在巨大差异。 static
属性将在您每次调用它时进行评估。 static readonly
稍作优化,因为它只做一次赋值。
让我们深入了解一下编译器对不同类型的字段做了什么。
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
创建一个 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)
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)
Expression bodied成员生成一个getter,其中returns常量值:
.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 属性 with initialization 为初始化值生成一个额外的支持字段。
.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)
一个属性getter全身长了一点:
.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)
据此,我们可以按它们生成的代码(和调用)数量对它们进行排序:
const string
绝对是最快的,但是当从其他组合使用时(如其他答案所述)static readonly
紧随其后,有一个字段访问权限static string ExpressionBodied => "xxx"
将导致方法调用 (getter) 只是 returns 一个常量static string GetInitialized {get;} = "xxx"
将导致方法调用和字段访问static string GetWithBody { get { return "xxx"; } }
将导致一个方法调用 returns 一个常量,但是如果有额外的内存分配,看起来
在实践中,性能差异可能无法观察到。正如所指出的,IL 代码可以通过 JIT 进一步优化,因此您最终可以获得有效的相同性能。 不过,我更喜欢选择选项 1. 或 2.