简体   繁体   English

=> 运算符在属性中的含义是什么?

[英]What does the => operator mean in a property?

I came across some code that said我遇到了一些代码说

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

Now I am somewhat familiar with Lambda expressions.现在我对 Lambda 表达式有点熟悉了。 I just have not seen it used it this way.我只是没有看到它以这种方式使用它。

What would be the difference between the above statement and上面的陈述和有什么区别

public int MaxHealth  = x ? y:z;

What you're looking at is an expression-bodied member not a lambda expression.您正在查看的是表达式主体成员而不是 lambda 表达式。

When the compiler encounters an expression-bodied property member, it essentially converts it to a getter like this:当编译器遇到表达式体属性成员时,它本质上将其转换为如下所示的 getter:

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

(You can verify this for yourself by pumping the code into a tool called TryRoslyn .) (您可以通过将代码注入一个名为TryRoslyn的工具来自己验证这一点。)

Expression-bodied members - like most C# 6 features - are just syntactic sugar .与大多数 C# 6 功能一样,表达式主体成员只是语法糖 This means that they don't provide functionality that couldn't otherwise be achieved through existing features.这意味着它们不提供通过现有功能无法实现的功能。 Instead, these new features allow a more expressive and succinct syntax to be used相反,这些新功能允许使用更具表现力和简洁的语法

As you can see, expression-bodied members have a handful of shortcuts that make property members more compact:如您所见,表达式体成员有一些快捷方式可以使属性成员更紧凑:

  • There is no need to use a return statement because the compiler can infer that you want to return the result of the expression无需使用return语句,因为编译器可以推断您想要返回表达式的结果
  • There is no need to create a statement block because the body is only one expression无需创建语句块,因为主体只是一个表达式
  • There is no need to use the get keyword because it is implied by the use of the expression-bodied member syntax.不需要使用get关键字,因为它是通过使用表达式体成员语法来暗示的。

I have made the final point bold because it is relevant to your actual question, which I will answer now.我将最后一点加粗,因为它与您的实际问题有关,我现在将回答。

The difference between...和...之间的不同...

// expression-bodied member property
public int MaxHealth => x ? y:z;

And...和...

// field with field initializer
public int MaxHealth = x ? y:z;

Is the same as the difference between...是不是一样的区别...

public int MaxHealth
{
    get
    {
        return x ? y:z;
    }
}

And...和...

public int MaxHealth = x ? y:z;

Which - if you understand properties - should be obvious.其中 - 如果您了解属性 - 应该是显而易见的。

Just to be clear, though: the first listing is a property with a getter under the hood that will be called each time you access it.不过要明确一点:第一个清单是一个在引擎盖下有一个 getter 的属性,每次访问它时都会调用它。 The second listing is is a field with a field initializer, whose expression is only evaluated once, when the type is instantiated.第二个清单是一个带有字段初始值设定项的字段,当类型被实例化时,它的表达式只计算一次。

This difference in syntax is actually quite subtle and can lead to a "gotcha" which is described by Bill Wagner in a post entitled "AC# 6 gotcha: Initialization vs. Expression Bodied Members" .这种语法上的差异实际上非常微妙,并且可能导致 Bill Wagner 在题为“AC# 6 gotcha: Initialization vs. Expression Bodied Members”的帖子中描述的“陷阱”

While expression-bodied members are lambda expression- like , they are not lambda expressions.虽然表达式体成员类似于lambda 表达式,但它们不是lambda 表达式。 The fundamental difference is that a lambda expression results in either a delegate instance or an expression tree.根本区别在于 lambda 表达式生成委托实例或表达式树。 Expression-bodied members are just a directive to the compiler to generate a property behind the scenes.表达式体成员只是编译器在幕后生成属性的指令。 The similarity (more or less) starts and end with the arrow ( => ).相似性(或多或少)以箭头( => )开始和结束。

I'll also add that expression-bodied members are not limited to property members.我还要补充一点,表达式体成员不仅限于属性成员。 They work on all these members:他们对所有这些成员起作用:

  • Properties特性
  • Indexers索引器
  • Methods方法
  • Operators运营商

Added in C# 7.0C# 7.0中添加

However, they do not work on these members:但是,它们不适用于以下成员:

  • Nested Types嵌套类型
  • Events活动
  • Fields字段

The => syntax is equal to the get { return ... } syntax. =>语法等同于get { return ... }语法。

string MyString { get; } = "value";

is not the same as不一样

string MyString => "value";

although in this case they are returning the same value in this case.尽管在这种情况下,它们在这种情况下返回相同的值。 Observe the two examples below.观察下面的两个例子。

Here's the difference.这就是区别。
Example 1 will return the same Person for every read of the property.示例 1 将为每次读取该属性返回相同的 Person。

Example 2 will return a new Person for every read of the property.示例 2 将为每次读取该属性返回一个新的 Person。

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

And...和...

//Ex: 2
public Person Person => new Person();

//Ex: 2
//Is the same as
public Person Person
{
    get { return new Person(); }
}

When you use the auto initializer the property creates the instance of value and uses that value persistently.当您使用自动初始化程序时,该属性会创建 value 的实例并永久使用该值。 In the above post there is a broken link to Bill Wagner, that explains this well, and I searched the correct link to understand it myself.在上面的帖子中,Bill Wagner 的链接断开了,这很好地解释了这一点,我搜索了正确的链接以自己理解它。

In my situation I had my property auto initialize a command in a ViewModel for a View.在我的情况下,我让我的属性在 ViewModel 中为视图自动初始化命令。 I changed the property to use expression bodied initializer and the command CanExecute stopped working.我将属性更改为使用表达式主体初始化程序,并且命令 CanExecute 停止工作。

Here's what it looked like and here's what was happening.这就是它的样子,这就是正在发生的事情。

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

here's what I changed it to.这就是我将其更改为的内容。

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

The difference here is when I use { get; } =这里的区别是当我使用{ get; } = { get; } = I create and reference the SAME command in that property. { get; } =我在该属性中创建并引用 SAME 命令。 When I use => I actually create a new command and return it every time the property is called.当我使用=>时,我实际上创建了一个新命令并在每次调用该属性时返回它。 Therefore, I could never update the CanExecute on my command because I was always telling it to update a new reference of that command.因此,我永远无法更新我的命令上的CanExecute ,因为我总是告诉它更新该命令的新引用。

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

All that said, if you are just pointing to a backing field then it works fine.综上所述,如果您只是指向支持字段,那么它可以正常工作。 This only happens when the auto or expression body creates the return value.这仅在自动或表达式主体创建返回值时发生。

This is a new feature of C# 6 called an expression bodied member that allows you to define a getter only property using a lambda like function.这是 C# 6 的一项新功能,称为表达式主体成员,它允许您使用类似 lambda 的函数定义仅 getter 属性。

While it is considered syntactic sugar for the following, they may not produce identical IL:虽然它被认为是以下语法糖,但它们可能不会产生相同的 IL:

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

It turns out that if you compile both versions of the above and compare the IL generated for each you'll see that they are NEARLY the same.事实证明,如果您编译上述两个版本并比较为每个版本生成的 IL,您会发现它们几乎相同。

Here is the IL for the classic version in this answer when defined in a class named TestClass :这是在名为TestClass的类中定义时,此答案中经典版本的 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

And here is the IL for the expression bodied member version when defined in a class named TestClass :这是在名为TestClass的类中定义的表达式主体成员版本的 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

See https://msdn.microsoft.com/en-us/magazine/dn802602.aspx for more information on this and other new features in C# 6.请参阅https://msdn.microsoft.com/en-us/magazine/dn802602.aspx ,了解有关 C# 6 中的此功能和其他新功能的更多信息。

See this post Difference between Property and Field in C# 3.0+ on the difference between a field and a property getter in C#.有关 C# 中字段和属性获取器之间的区别,请参阅这篇文章C# 3.0+ 中的属性和字段之间的区别。

Update:更新:

Note that expression-bodied members were expanded to include properties, constructors, finalizers and indexers in C# 7.0.请注意,表达式体成员已扩展为包括 C# 7.0 中的属性、构造函数、终结器和索引器。

It is called Expression Bodied Member and it was introduced in C# 6. It is merely syntactic sugar over a get only property.它被称为Expression Bodied Member ,它是在 C# 6 中引入的。它只是get only 属性的语法糖。

It is equivalent to:它相当于:

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

An equivalent of a method declaration is avaliable:等效的方法声明是可用的:

public string HelloWorld() => "Hello World";

Mainly allowing you shortening of boilerplate.主要是让你缩短样板。

One other significant point if you're using C# 6:如果您使用的是 C# 6,还有一个重要的点:

'=>' can be used instead of 'get' and is only for 'get only' methods - it can't be used with a 'set'. '=>' 可以用来代替 'get' 并且用于 'get only' 方法- 它不能与 'set' 一起使用。

For C# 7, see the comment from @avenmore below - it can now be used in more places.对于 C# 7,请参阅下面 @avenmore 的评论 - 它现在可以在更多地方使用。 Here's a good reference - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/这是一个很好的参考 - https://csharp.christiannagel.com/2017/01/25/expressionbodiedmembers/

For the following statement shared by Alex Booker in their answer对于亚历克斯布克他们的回答中分享的以下陈述

When the compiler encounters an expression-bodied property member, it essentially converts it to a getter like this:当编译器遇到表达式体属性成员时,它本质上将其转换为如下所示的 getter:

Please see the following screenshot , it shows how this statement (using SharpLab link )请看下面的截图,它显示了这个语句(使用SharpLab 链接

public string APIBasePath => Configuration.ToolsAPIBasePath;

converts to转换为

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

Screenshot:截屏: 在此处输入图像描述

You can also use the arrow for the get accessor:您还可以使用get访问器的箭头:

    private string foo = "foo";

    private string bar
    {
        get => $"{foo}bar";
        set
        {
            foo = value;
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM