属性签名中C#中的=>赋值是什么


229

我遇到了一些说的代码

public int MaxHealth => 
         Memory[Address].IsValid ? 
         Memory[Address].Read<int>(Offs.Life.MaxHp) : 
         0;

现在,我对Lambda表达式有所了解。我只是没有看到它以这种方式使用。

上面的陈述和

public int MaxHealth  = x ? y:z;

4
第一块是属性,第二块是变量
M.kazem Akhgary

14
@ M.kazemAkhgary *一个字段,不是变量。
马菲

Answers:


376

您正在查看的是表达式健全的成员, 而不是lambda表达式。

当编译器遇到表达式表达式属性成员时,它实际上将其转换为如下所示的getter:

public int MaxHealth
{
    get
    {
        return Memory[Address].IsValid ? Memory[Address].Read<int>(Offs.Life.MaxHp) : 0;
    }
}

(您可以通过将代码注入到一个名为TryRoslyn的工具中来亲自验证这一点。)

像大多数C#6功能一样,表达有力的成员只是 语法糖。这意味着它们不会提供通过现有功能无法实现的功能。相反,这些新功能允许使用更具表现力和简洁的语法

如您所见,表达式强健的成员具有一些使属性成员更加紧凑的快捷方式:

  • 不需要使用return语句,因为编译器可以推断出您要返回表达式的结果
  • 无需创建语句块,因为主体只是一个表达式
  • 无需使用get关键字,因为使用表达式主体成员语法隐含了该关键字。

我将最后一点加粗,因为它与您的实际问题相关,我现在将回答。

和...之间的不同...

// 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;

如果您了解属性,则应该显而易见。

不过,要清楚一点:第一个清单是一个带有吸气罩的属性,每次访问该属性都会被调用。第二个清单是带有字段初始值设定项的字段,当实例化类型时,其表达式仅计算一次。

语法上的这种差异实际上非常微妙,并且可能导致“陷阱”,这由Bill Wagner在标题为“ AC#6陷阱:初始化与表达式主体成员”的帖子中进行了描述。

虽然表达式健全的成员是lambda expression- like,但它们不是 lambda表达式。根本区别在于,lambda表达式会导致委托实例或表达式树。表达式主体成员只是编译器的指令,可在幕后生成属性。相似度(或多或少)以箭头(=>)开始和结束。

我还要补充一点,表达体成员不仅限于属性成员。他们在所有这些成员上工作:

  • 物产
  • 索引器
  • 方法
  • 经营者

C#7.0中添加

但是,它们不适用于以下成员:

  • 嵌套类型
  • 大事记
  • 领域

6
从C#7开始,还支持构造函数和终结器。docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…–
bzier

8
@bzier让我们成为函数式程序员是一个阴谋。如果然后再永远!
Sentinel

超级棒的答案!
Jaime Arroyo Garcia '18

2
Bill Wagner的帖子的链接当前已断开。我想我找到了新的URL:codeproject.com/Articles/1064964/...
弗莱辛普森

36

好的...我评论说它们是不同的,但无法确切解释如何,但现在我知道了。

String Property { get; } = "value";

与...不同

String Property => "value";

就是这里...

使用自动初始化程序时,该属性创建value的实例并永久使用该值。在上面的帖子中,有一个指向Bill Wagner的断开链接,这很好地说明了这一点,我搜索了正确的链接以自己理解。

在我的情况下,我的属性自动在ViewModel的ViewModel中初始化命令。我将属性更改为使用表达式刚强的初始值设定项,命令CanExecute停止工作。

这是它的外观,这是正在发生的事情。

Command MyCommand { get; } = new Command();  //works

这是我将其更改为的内容。

Command MyCommand => new Command();  //doesn't work properly

区别在于,当我使用{ get; } =该属性时,我创建并引用了SAME命令。当我使用时=>,实际上创建了一个新命令,并在每次调用该属性时将其返回。因此,我永远无法更新CanExecute命令,因为我总是告诉它更新该命令的新引用。

{ get; } = // same reference
=>         // new reference

综上所述,如果您只是指向备用字段,那么它就可以正常工作。仅当auto或expression主体创建返回值时,才会发生这种情况。


8
=>语法等于get {return new Command(); } 句法。
马菲

35

这是C#6的一项新功能,称为表达式主体成员,它使您可以使用类似于lambda的函数来定义仅getter属性。

尽管以下情况被认为是语法糖,但它们可能不会产生相同的IL:

public int MaxHealth
{
    get
    {
        return Memory[Address].IsValid
               ?   Memory[Address].Read<int>(Offs.Life.MaxHp)
               :   0;
    }
}

事实证明,如果同时编译上述两个版本并比较为每个版本生成的IL,您会发现它们几乎相同。

这是当在名为class的类中定义时,此答案中经典版本的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

这是当在名为的类中定义表达式时,成员身体版本的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 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/zh-cn/magazine/dn802602.aspx

有关C#中的字段与属性获取器之间的区别,请参见C#3.0+中的属性与字段之间的区别。

更新:

请注意,在C#7.0中,表达式主体成员已扩展为包括属性,构造函数,终结器和索引器。


16

它被称为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";

主要允许您缩短样板。


7

如果您使用的是C#6,还有一点很重要:

'=>'可以代替'get'使用,并且用于'get only'方法 -不能与'set'一起使用。

对于C#7,请参阅下面@avenmore中的注释-现在可以在更多地方使用它。这是一个很好的参考-https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/


8
如果您使用的是C#7,则不再适用。“ C#7.0继续提高了生产率。C#6中提供了表达式体成员用于方法和属性,现在它们可与构造函数,析构函数,属性访问器和事件访问器一起使用也是。” (来源
avenmore

1

对于Alex Booker其回答中分享的以下声明

当编译器遇到表达式表达式属性成员时,它实际上将其转换为如下所示的getter:

请查看以下屏幕截图,它显示了此语句的方式(使用SharpLab链接

public string APIBasePath => Configuration.ToolsAPIBasePath;

转换为

public string APIBasePath
{
    get
    {
        return Configuration.ToolsAPIBasePath;
    }
}

屏幕截图: 在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.