การมอบหมาย => ใน C # ในลายเซ็นคุณสมบัติคืออะไร


229

ฉันเจอรหัสที่บอกว่า

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

ตอนนี้ฉันค่อนข้างคุ้นเคยกับการแสดงออกของแลมบ์ดาแล้ว ฉันไม่ได้เห็นมันใช้วิธีนี้

สิ่งที่จะเป็นความแตกต่างระหว่างคำสั่งข้างต้นและ

public int MaxHealth  = x ? y:z;

4
บล็อกแรกคือคุณสมบัติที่สองคือตัวแปร
M.kazem Akhgary

14
@ M.kazemAkhgary * a ฟิลด์ไม่ใช่ตัวแปร
Mafii

คำตอบ:


376

สิ่งที่คุณกำลังดูคือสมาชิกที่ มีการแสดงออกไม่ใช่การแสดงออกแลมบ์ดา

เมื่อคอมไพเลอร์พบสมาชิกพร็อพเพอร์ตี้ที่มีแอลกอฮอล์มันจะแปลงมันเป็นทะเยอทะยานเช่นนี้

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;

ซึ่ง - ถ้าคุณเข้าใจคุณสมบัติ - ควรชัดเจน

เพื่อให้ชัดเจนแม้ว่า: รายชื่อแรกเป็นทรัพย์สินที่มีทะเยอทะยานภายใต้ประทุนที่จะถูกเรียกทุกครั้งที่คุณเข้าถึง รายการที่สองคือฟิลด์ที่มี initializer ฟิลด์ซึ่งนิพจน์จะถูกประเมินเพียงครั้งเดียวเมื่อประเภทถูกสร้างอินสแตนซ์

ความแตกต่างในรูปแบบนี้เป็นจริงที่ลึกซึ้งมากและสามารถนำไปสู่ "gotcha" ซึ่งจะอธิบายโดยบิลแว็กเนอร์ในโพสต์ที่มีชื่อว่า"เอซี # 6 gotcha: การเริ่มต้นกับการแสดงออก Bodied สมาชิก"

ขณะที่สมาชิกแสดงออกฉกรรจ์มีแลมบ์ดา expression- เหมือนพวกเขาจะไม่ได้แสดงออกแลมบ์ดา ความแตกต่างพื้นฐานคือการแสดงออกแลมบ์ดาส่งผลทั้งในกรณีตัวแทนหรือต้นไม้การแสดงออก สมาชิก Expression-bodied เป็นเพียงคำสั่งให้คอมไพเลอร์เพื่อสร้างสถานที่ให้บริการที่อยู่เบื้องหลัง ความคล้ายคลึงกัน (มากหรือน้อย) เริ่มต้นและสิ้นสุดด้วยลูกศร ( =>)

ฉันจะเพิ่มว่าสมาชิกที่มีการแสดงออกไม่ได้ จำกัด อยู่ที่สมาชิกอสังหาริมทรัพย์เท่านั้น พวกเขาทำงานกับสมาชิกเหล่านี้ทั้งหมด:

  • คุณสมบัติ
  • indexers
  • วิธีการ
  • ผู้ประกอบการ

เพิ่มในC # 7.0

อย่างไรก็ตามพวกเขาไม่ได้ทำงานกับสมาชิกเหล่านี้:

  • ประเภทซ้อนกัน
  • เหตุการณ์ที่เกิดขึ้น
  • ทุ่ง

6
ตั้งแต่ C # 7 คอนสตรัคเตอร์และ finalizers ก็รองรับเช่นกัน docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…
bzier

8
@bzier มันเป็นการสมคบคิดที่จะทำให้พวกเราเป็นโปรแกรมเมอร์ ถ้าอื่นแล้วตลอดไป !!
Sentinel

คำตอบที่ยอดเยี่ยมมาก!
Jaime Arroyo Garcia

2
ลิงก์ไปยังโพสต์ของ Bill Wagner เสียแล้ว ฉันคิดว่าฉันได้พบ URL ใหม่: codeproject.com/Articles/1064964/…
Fry Simpson

36

ตกลง ... ฉันแสดงความคิดเห็นว่าพวกเขาแตกต่างกัน แต่ไม่สามารถอธิบายได้อย่างชัดเจนว่าอย่างไร

String Property { get; } = "value";

ไม่เหมือนกัน

String Property => "value";

นี่คือความแตกต่าง ...

เมื่อคุณใช้ initializer อัตโนมัติคุณสมบัติจะสร้างอินสแตนซ์ของค่าและใช้ค่านั้นอย่างต่อเนื่อง ในโพสต์ด้านบนมีลิงก์ที่เสียหายไปยัง Bill Wagner ที่อธิบายสิ่งนี้ได้ดีและฉันค้นหาลิงก์ที่ถูกต้องเพื่อทำความเข้าใจด้วยตนเอง

ในสถานการณ์ของฉันฉันมีสถานที่ให้บริการอัตโนมัติของฉันเริ่มต้นคำสั่งใน ViewModel สำหรับมุมมอง ฉันเปลี่ยนคุณสมบัติเพื่อใช้ expression initialied bodied และคำสั่ง CanExecute หยุดทำงาน

นี่คือสิ่งที่ดูเหมือนและนี่คือสิ่งที่เกิดขึ้น

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

นี่คือสิ่งที่ฉันเปลี่ยนเป็น

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

ความแตกต่างที่นี่คือเมื่อฉันใช้{ get; } =ฉันสร้างและอ้างอิงคำสั่งเดียวกันในคุณสมบัตินั้น เมื่อฉันใช้=>ฉันสร้างคำสั่งใหม่และส่งกลับทุกครั้งที่เรียกคุณสมบัติ ดังนั้นฉันไม่สามารถอัปเดตCanExecuteคำสั่งของฉันได้เพราะฉันมักจะบอกให้อัปเดตการอ้างอิงใหม่ของคำสั่งนั้น

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

ทุกอย่างที่กล่าวว่าถ้าคุณเพิ่งชี้ไปที่เขตข้อมูลสำรองมันก็ใช้ได้ดี สิ่งนี้จะเกิดขึ้นก็ต่อเมื่อ auto หรือ expression เนื้อสร้างค่าตอบแทน


8
ไวยากรณ์ => เท่ากับ get {return new Command (); } ไวยากรณ์
Mafii

35

นี่คือคุณสมบัติใหม่ของ C # 6 ที่เรียกว่าการแสดงออกของสมาชิกที่ช่วยให้คุณสามารถกำหนดคุณสมบัติ getter เท่านั้นโดยใช้แลมบ์ดาเช่นฟังก์ชั่น

ในขณะที่มันถูกพิจารณาว่าเป็นน้ำตาล syntacticพวกเขาอาจจะไม่เหมือนอิลลินอยส์:

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

ปรากฎว่าหากคุณรวบรวมทั้งสองเวอร์ชันข้างต้นและเปรียบเทียบ IL ที่สร้างขึ้นสำหรับแต่ละรุ่นคุณจะเห็นว่าพวกเขาเหมือนกันเกือบทั้งหมด

นี่คือ 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

ดูhttps://msdn.microsoft.com/en-us/magazine/dn802602.aspxสำหรับข้อมูลเพิ่มเติมเกี่ยวกับสิ่งนี้และคุณสมบัติใหม่อื่น ๆ ใน C # 6

ดูโพสต์นี้ความแตกต่างระหว่างพร็อพเพอร์ตี้และฟิลด์ใน C # 3.0 ขึ้นไปกับความแตกต่างระหว่างฟิลด์และพร็อพเพอร์ตี้คุณสมบัติใน C #

ปรับปรุง:

โปรดทราบว่าสมาชิกที่มีการแสดงออกมีการขยายเพื่อรวมคุณสมบัติคอนสตรัคเตอร์ผู้สร้างขั้นสุดท้ายและดัชนีใน C # 7.0


16

มันถูกเรียกว่าExpression Bodied Memberและมันถูกนำมาใช้ใน C # 6 มันเป็นเพียงน้ำตาล syntactic มากกว่า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 ดำเนินการต่อด้วยการปรับปรุงประสิทธิภาพสมาชิก Expression-bodied พร้อมใช้งานกับ C # 6 สำหรับวิธีการและคุณสมบัติ เช่นกัน " ( ที่มา )
avenmore

1

สำหรับคำสั่งต่อไปนี้แบ่งปันโดยAlex Bookerในคำตอบของพวกเขา

เมื่อคอมไพเลอร์พบสมาชิกพร็อพเพอร์ตี้ที่มีแอลกอฮอล์มันจะแปลงมันเป็นทะเยอทะยานเช่นนี้

โปรดดูภาพหน้าจอต่อไปนี้มันแสดงให้เห็นว่าคำสั่งนี้ (ใช้ลิงค์ SharpLab )

public string APIBasePath => Configuration.ToolsAPIBasePath;

แปลงเป็น

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

ภาพหน้าจอ: ป้อนคำอธิบายรูปภาพที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.