属性 中的 => 运算符是什么意思?
What does the => operator mean in a property?
我遇到了一些代码
public int MaxHealth =>
Memory[Address].IsValid ?
Memory[Address].Read<int>(Offs.Life.MaxHp) :
0;
现在我对Lambda表达式有些熟悉了。我只是没有看到它是这样使用的。
上面的说法和
有什么区别
public int MaxHealth = x ? y:z;
它被称为 Expression Bodied Member,它是在 C# 6 中引入的。它只是 get
的语法糖 属性。
相当于:
public int MaxHealth { get { return Memory[Address].IsValid ?
Memory[Address].Read<int>(Offs.Life.MaxHp) : 0; }
方法声明的等价物可用:
public string HelloWorld() => "Hello World";
主要是让您缩短样板文件。
这是 C# 6 的一项新功能,称为表达式主体成员,允许您使用类似 lambda 的函数仅 属性 定义 getter。
虽然以下情况被认为是 syntactic sugar,但它们 可能不会 产生相同的 IL:
public int MaxHealth
{
get
{
return Memory[Address].IsValid
? Memory[Address].Read<int>(Offs.Life.MaxHp)
: 0;
}
}
事实证明,如果您编译上述两个版本并比较为每个版本生成的 IL,您会发现它们 几乎 相同。
这是在名为 TestClass
:
的 class 中定义时此答案中 classic 版本的 IL
.property instance int32 MaxHealth()
{
.get instance int32 TestClass::get_MaxHealth()
}
.method public hidebysig specialname
instance int32 get_MaxHealth () cil managed
{
// Method begins at RVA 0x2458
// Code size 71 (0x47)
.maxstack 2
.locals init (
[0] int32
)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
IL_0007: ldarg.0
IL_0008: ldfld int64 TestClass::Address
IL_000d: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
IL_0012: ldfld bool MemoryAddress::IsValid
IL_0017: brtrue.s IL_001c
IL_0019: ldc.i4.0
IL_001a: br.s IL_0042
IL_001c: ldarg.0
IL_001d: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
IL_0022: ldarg.0
IL_0023: ldfld int64 TestClass::Address
IL_0028: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
IL_002d: ldarg.0
IL_002e: ldfld class Offs TestClass::Offs
IL_0033: ldfld class Life Offs::Life
IL_0038: ldfld int64 Life::MaxHp
IL_003d: callvirt instance !!0 MemoryAddress::Read<int32>(int64)
IL_0042: stloc.0
IL_0043: br.s IL_0045
IL_0045: ldloc.0
IL_0046: ret
} // end of method TestClass::get_MaxHealth
这是在名为 TestClass
:
的 class 中定义的表达式主体成员版本的 IL
.property instance int32 MaxHealth()
{
.get instance int32 TestClass::get_MaxHealth()
}
.method public hidebysig specialname
instance int32 get_MaxHealth () cil managed
{
// Method begins at RVA 0x2458
// Code size 66 (0x42)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
IL_0006: ldarg.0
IL_0007: ldfld int64 TestClass::Address
IL_000c: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
IL_0011: ldfld bool MemoryAddress::IsValid
IL_0016: brtrue.s IL_001b
IL_0018: ldc.i4.0
IL_0019: br.s IL_0041
IL_001b: ldarg.0
IL_001c: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
IL_0021: ldarg.0
IL_0022: ldfld int64 TestClass::Address
IL_0027: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
IL_002c: ldarg.0
IL_002d: ldfld class Offs TestClass::Offs
IL_0032: ldfld class Life Offs::Life
IL_0037: ldfld int64 Life::MaxHp
IL_003c: callvirt instance !!0 MemoryAddress::Read<int32>(int64)
IL_0041: ret
} // end of method TestClass::get_MaxHealth
有关此功能和 C# 6 中其他新功能的更多信息,请参阅 https://msdn.microsoft.com/en-us/magazine/dn802602.aspx。
请参阅此 post Difference between Property and Field in C# 3.0+,了解 C# 中字段与 属性 getter 的区别。
更新:
请注意,在 C# 7.0 中,表达式体成员已扩展为包括属性、构造函数、终结器和索引器。
您看到的是 expression-bodied member 而不是 lambda 表达式。
当编译器遇到 expression-bodied 属性 成员时,它实际上将其转换为 getter,如下所示:
public int MaxHealth
{
get
{
return Memory[Address].IsValid ? Memory[Address].Read<int>(Offs.Life.MaxHp) : 0;
}
}
(您可以通过将代码输入名为 TryRoslyn 的工具来自行验证这一点。)
Expression-bodied 成员 - 与大多数 C# 6 功能一样 - 只是 syntactic sugar。这意味着它们不提供无法通过现有功能实现的功能。相反,这些新功能允许使用更具表现力和简洁的语法
如您所见,expression-bodied 成员有一些快捷方式可以使 属性 成员更加紧凑:
- 不需要使用
return
语句,因为编译器可以推断出你想要return表达式的结果
- 不需要创建语句块,因为body只有一个表达式
- 不需要使用
get
关键字,因为使用 expression-bodied 成员语法暗示它。
我将最后一点加粗是因为它与你的实际问题相关,我现在将回答这个问题。
两者的区别...
// expression-bodied member property
public int MaxHealth => x ? y:z;
还有...
// field with field initializer
public int MaxHealth = x ? y:z;
相同的区别...
public int MaxHealth
{
get
{
return x ? y:z;
}
}
还有...
public int MaxHealth = x ? y:z;
哪个 - 如果您了解属性 - 应该是显而易见的。
不过要明确一点:第一个列表是一个 属性,在后台有一个 getter,每次访问它时都会调用它。第二个清单是一个带有字段初始值设定项的字段,当类型被实例化时,其表达式仅被计算一次。
语法上的这种差异实际上非常微妙,可能导致 "gotcha",Bill Wagner 在标题为 "A C# 6 gotcha: Initialization vs. Expression Bodied Members".
的 post 中对其进行了描述
虽然 expression-bodied 成员是 lambda 表达式-like,但它们是 not lambda 表达式。根本区别在于 lambda 表达式生成委托实例或表达式树。 Expression-bodied 成员只是编译器在幕后生成 属性 的指令。相似性(或多或少)以箭头 (=>
) 开始和结束。
我还要补充一点,expression-bodied 成员不限于 属性 成员。他们在所有这些成员上工作:
- 属性
- 索引器
- 方法
- 运算符
已添加到 C# 7.0
但是,它们不适用于这些成员:
- 嵌套类型
- 活动
- 字段
=>
语法等同于 get { return ... }
语法。
string MyString { get; } = "value";
与
不一样
string MyString => "value";
尽管在这种情况下它们 return 在这种情况下具有相同的值。观察下面的两个例子。
这是不同之处。
示例 1 将 return 每次读取 属性.
相同的人
示例 2 将 return 每次读取 属性 的新人物。
//Ex: 1
public Person Person { get; } = new Person();
//Ex: 1
//Is the same as
private readonly Person person = new Person();
public Person Person
{
get { return person; }
}
还有...
//Ex: 2
public Person Person => new Person();
//Ex: 2
//Is the same as
public Person Person
{
get { return new Person(); }
}
当您使用自动初始化程序时,属性 会创建值的实例并永久使用该值。上面的post有一个Bill Wagner的破link,解释的很好,我自己搜索了正确的link来理解。
在我的情况下,我 属性 自动初始化视图模型中的命令。我将 属性 更改为使用表达式主体初始化程序,并且命令 CanExecute 停止工作。
这是它的样子,这是正在发生的事情。
Command MyCommand { get; } = new Command(); //works
这是我改成的。
Command MyCommand => new Command(); //doesn't work properly
不同之处在于,当我使用 { get; } =
时,我在 属性 中创建并引用了 SAME 命令。当我使用 =>
时,我实际上创建了一个新命令,并在每次调用 属性 时创建了一个新命令 return。因此,我永远无法更新命令中的 CanExecute
,因为我总是告诉它更新该命令的新引用。
{ get; } = // same reference
=> // new reference
综上所述,如果您只是指向一个支持字段,那么它工作正常。这仅在自动或表达式主体创建 return 值时发生。
如果您使用的是 C# 6,还有一点很重要:
'=>' 可以代替 'get' 并且 仅 用于 'get only' 方法 -它不能与 'set'.
一起使用
对于 C# 7,请参阅下面@avenmore 的评论 - 它现在可以在更多地方使用。这是一个很好的参考 - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/
对于Alex Booker in
分享的以下声明
When the compiler encounters an expression-bodied property member, it essentially converts it to a getter like this:
请看下面的截图,它显示了这个语句(使用SharpLab link)
public string APIBasePath => Configuration.ToolsAPIBasePath;
转换为
public string APIBasePath
{
get
{
return Configuration.ToolsAPIBasePath;
}
}
截图:
您还可以使用 get
访问器的箭头:
private string foo = "foo";
private string bar
{
get => $"{foo}bar";
set
{
foo = value;
}
}
我遇到了一些代码
public int MaxHealth =>
Memory[Address].IsValid ?
Memory[Address].Read<int>(Offs.Life.MaxHp) :
0;
现在我对Lambda表达式有些熟悉了。我只是没有看到它是这样使用的。
上面的说法和
有什么区别public int MaxHealth = x ? y:z;
它被称为 Expression Bodied Member,它是在 C# 6 中引入的。它只是 get
的语法糖 属性。
相当于:
public int MaxHealth { get { return Memory[Address].IsValid ?
Memory[Address].Read<int>(Offs.Life.MaxHp) : 0; }
方法声明的等价物可用:
public string HelloWorld() => "Hello World";
主要是让您缩短样板文件。
这是 C# 6 的一项新功能,称为表达式主体成员,允许您使用类似 lambda 的函数仅 属性 定义 getter。
虽然以下情况被认为是 syntactic sugar,但它们 可能不会 产生相同的 IL:
public int MaxHealth
{
get
{
return Memory[Address].IsValid
? Memory[Address].Read<int>(Offs.Life.MaxHp)
: 0;
}
}
事实证明,如果您编译上述两个版本并比较为每个版本生成的 IL,您会发现它们 几乎 相同。
这是在名为 TestClass
:
.property instance int32 MaxHealth()
{
.get instance int32 TestClass::get_MaxHealth()
}
.method public hidebysig specialname
instance int32 get_MaxHealth () cil managed
{
// Method begins at RVA 0x2458
// Code size 71 (0x47)
.maxstack 2
.locals init (
[0] int32
)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
IL_0007: ldarg.0
IL_0008: ldfld int64 TestClass::Address
IL_000d: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
IL_0012: ldfld bool MemoryAddress::IsValid
IL_0017: brtrue.s IL_001c
IL_0019: ldc.i4.0
IL_001a: br.s IL_0042
IL_001c: ldarg.0
IL_001d: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
IL_0022: ldarg.0
IL_0023: ldfld int64 TestClass::Address
IL_0028: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
IL_002d: ldarg.0
IL_002e: ldfld class Offs TestClass::Offs
IL_0033: ldfld class Life Offs::Life
IL_0038: ldfld int64 Life::MaxHp
IL_003d: callvirt instance !!0 MemoryAddress::Read<int32>(int64)
IL_0042: stloc.0
IL_0043: br.s IL_0045
IL_0045: ldloc.0
IL_0046: ret
} // end of method TestClass::get_MaxHealth
这是在名为 TestClass
:
.property instance int32 MaxHealth()
{
.get instance int32 TestClass::get_MaxHealth()
}
.method public hidebysig specialname
instance int32 get_MaxHealth () cil managed
{
// Method begins at RVA 0x2458
// Code size 66 (0x42)
.maxstack 2
IL_0000: ldarg.0
IL_0001: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
IL_0006: ldarg.0
IL_0007: ldfld int64 TestClass::Address
IL_000c: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
IL_0011: ldfld bool MemoryAddress::IsValid
IL_0016: brtrue.s IL_001b
IL_0018: ldc.i4.0
IL_0019: br.s IL_0041
IL_001b: ldarg.0
IL_001c: ldfld class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress> TestClass::Memory
IL_0021: ldarg.0
IL_0022: ldfld int64 TestClass::Address
IL_0027: callvirt instance !1 class [mscorlib]System.Collections.Generic.Dictionary`2<int64, class MemoryAddress>::get_Item(!0)
IL_002c: ldarg.0
IL_002d: ldfld class Offs TestClass::Offs
IL_0032: ldfld class Life Offs::Life
IL_0037: ldfld int64 Life::MaxHp
IL_003c: callvirt instance !!0 MemoryAddress::Read<int32>(int64)
IL_0041: ret
} // end of method TestClass::get_MaxHealth
有关此功能和 C# 6 中其他新功能的更多信息,请参阅 https://msdn.microsoft.com/en-us/magazine/dn802602.aspx。
请参阅此 post Difference between Property and Field in C# 3.0+,了解 C# 中字段与 属性 getter 的区别。
更新:
请注意,在 C# 7.0 中,表达式体成员已扩展为包括属性、构造函数、终结器和索引器。
您看到的是 expression-bodied member 而不是 lambda 表达式。
当编译器遇到 expression-bodied 属性 成员时,它实际上将其转换为 getter,如下所示:
public int MaxHealth
{
get
{
return Memory[Address].IsValid ? Memory[Address].Read<int>(Offs.Life.MaxHp) : 0;
}
}
(您可以通过将代码输入名为 TryRoslyn 的工具来自行验证这一点。)
Expression-bodied 成员 - 与大多数 C# 6 功能一样 - 只是 syntactic sugar。这意味着它们不提供无法通过现有功能实现的功能。相反,这些新功能允许使用更具表现力和简洁的语法
如您所见,expression-bodied 成员有一些快捷方式可以使 属性 成员更加紧凑:
- 不需要使用
return
语句,因为编译器可以推断出你想要return表达式的结果 - 不需要创建语句块,因为body只有一个表达式
- 不需要使用
get
关键字,因为使用 expression-bodied 成员语法暗示它。
我将最后一点加粗是因为它与你的实际问题相关,我现在将回答这个问题。
两者的区别...
// expression-bodied member property
public int MaxHealth => x ? y:z;
还有...
// field with field initializer
public int MaxHealth = x ? y:z;
相同的区别...
public int MaxHealth
{
get
{
return x ? y:z;
}
}
还有...
public int MaxHealth = x ? y:z;
哪个 - 如果您了解属性 - 应该是显而易见的。
不过要明确一点:第一个列表是一个 属性,在后台有一个 getter,每次访问它时都会调用它。第二个清单是一个带有字段初始值设定项的字段,当类型被实例化时,其表达式仅被计算一次。
语法上的这种差异实际上非常微妙,可能导致 "gotcha",Bill Wagner 在标题为 "A C# 6 gotcha: Initialization vs. Expression Bodied Members".
的 post 中对其进行了描述虽然 expression-bodied 成员是 lambda 表达式-like,但它们是 not lambda 表达式。根本区别在于 lambda 表达式生成委托实例或表达式树。 Expression-bodied 成员只是编译器在幕后生成 属性 的指令。相似性(或多或少)以箭头 (=>
) 开始和结束。
我还要补充一点,expression-bodied 成员不限于 属性 成员。他们在所有这些成员上工作:
- 属性
- 索引器
- 方法
- 运算符
已添加到 C# 7.0
但是,它们不适用于这些成员:
- 嵌套类型
- 活动
- 字段
=>
语法等同于 get { return ... }
语法。
string MyString { get; } = "value";
与
不一样string MyString => "value";
尽管在这种情况下它们 return 在这种情况下具有相同的值。观察下面的两个例子。
这是不同之处。
示例 1 将 return 每次读取 属性.
示例 2 将 return 每次读取 属性 的新人物。
//Ex: 1
public Person Person { get; } = new Person();
//Ex: 1
//Is the same as
private readonly Person person = new Person();
public Person Person
{
get { return person; }
}
还有...
//Ex: 2
public Person Person => new Person();
//Ex: 2
//Is the same as
public Person Person
{
get { return new Person(); }
}
当您使用自动初始化程序时,属性 会创建值的实例并永久使用该值。上面的post有一个Bill Wagner的破link,解释的很好,我自己搜索了正确的link来理解。
在我的情况下,我 属性 自动初始化视图模型中的命令。我将 属性 更改为使用表达式主体初始化程序,并且命令 CanExecute 停止工作。
这是它的样子,这是正在发生的事情。
Command MyCommand { get; } = new Command(); //works
这是我改成的。
Command MyCommand => new Command(); //doesn't work properly
不同之处在于,当我使用 { get; } =
时,我在 属性 中创建并引用了 SAME 命令。当我使用 =>
时,我实际上创建了一个新命令,并在每次调用 属性 时创建了一个新命令 return。因此,我永远无法更新命令中的 CanExecute
,因为我总是告诉它更新该命令的新引用。
{ get; } = // same reference
=> // new reference
综上所述,如果您只是指向一个支持字段,那么它工作正常。这仅在自动或表达式主体创建 return 值时发生。
如果您使用的是 C# 6,还有一点很重要:
'=>' 可以代替 'get' 并且 仅 用于 'get only' 方法 -它不能与 'set'.
一起使用对于 C# 7,请参阅下面@avenmore 的评论 - 它现在可以在更多地方使用。这是一个很好的参考 - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/
对于Alex Booker in
When the compiler encounters an expression-bodied property member, it essentially converts it to a getter like this:
请看下面的截图,它显示了这个语句(使用SharpLab link)
public string APIBasePath => Configuration.ToolsAPIBasePath;
转换为
public string APIBasePath
{
get
{
return Configuration.ToolsAPIBasePath;
}
}
截图:
您还可以使用 get
访问器的箭头:
private string foo = "foo";
private string bar
{
get => $"{foo}bar";
set
{
foo = value;
}
}