Việc gán => trong C # trong chữ ký thuộc tính là gì


229

Tôi đã xem qua một số mã cho biết

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

Bây giờ tôi có phần quen thuộc với các biểu thức Lambda. Tôi chỉ không thấy nó sử dụng nó theo cách này.

Điều gì sẽ là sự khác biệt giữa tuyên bố trên và

public int MaxHealth  = x ? y:z;

4
khối đầu tiên là tài sản thứ hai là biến
M.kazem Akhÿ

14
@ M.kazemAkhÿ * một lĩnh vực, không phải là một biến.
Mafii

Câu trả lời:


376

Những gì bạn đang nhìn là một thành viên thể hiện không phải là một biểu hiện lambda.

Khi trình biên dịch gặp một thành viên thuộc tính biểu thức , về cơ bản nó sẽ chuyển đổi nó thành một getter như thế này:

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

(Bạn có thể tự xác minh điều này bằng cách bơm mã vào một công cụ có tên là TryRoslyn .)

Các thành viên thân thể biểu hiện - giống như hầu hết các tính năng C # 6 - chỉ là đường cú pháp . Điều này có nghĩa là họ không cung cấp chức năng mà không thể đạt được thông qua các tính năng hiện có. Thay vào đó, các tính năng mới này cho phép sử dụng cú pháp ngắn gọn và biểu cảm hơn

Như bạn có thể thấy, các thành viên thể hiện có một số phím tắt giúp các thành viên thuộc tính gọn hơn:

  • Không cần sử dụng returncâu lệnh vì trình biên dịch có thể suy ra rằng bạn muốn trả về kết quả của biểu thức
  • Không cần tạo khối lệnh vì cơ thể chỉ có một biểu thức
  • Không cần sử dụng gettừ khóa vì nó được ngụ ý bởi việc sử dụng cú pháp thành viên biểu thức.

Tôi đã làm cho điểm cuối cùng được in đậm vì nó có liên quan đến câu hỏi thực tế của bạn, mà tôi sẽ trả lời ngay bây giờ.

Sự khác biệt giữa ...

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

Và ...

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

Có giống như sự khác biệt giữa ...

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

Và ...

public int MaxHealth = x ? y:z;

Mà - nếu bạn hiểu thuộc tính - nên rõ ràng.

Mặc dù vậy, chỉ cần rõ ràng: danh sách đầu tiên là một tài sản với một getter dưới mui xe sẽ được gọi mỗi khi bạn truy cập nó. Danh sách thứ hai là một trường có trình khởi tạo trường, biểu thức chỉ được ước tính một lần, khi kiểu được khởi tạo.

Sự khác biệt về cú pháp này thực sự khá tinh tế và có thể dẫn đến một "gotcha" được Bill Wagner mô tả trong một bài đăng có tựa đề "AC # 6 gotcha: Khởi tạo so với các thành viên thân thể biểu hiện" .

Mặc dù các thành viên thân biểu hiện giống như biểu thức lambda , nhưng chúng không phải là biểu thức lambda. Sự khác biệt cơ bản là biểu thức lambda dẫn đến một thể hiện ủy nhiệm hoặc cây biểu thức. Các thành viên thể hiện chỉ là một chỉ thị cho trình biên dịch để tạo ra một thuộc tính đằng sau hậu trường. Sự giống nhau (nhiều hơn hoặc ít hơn) bắt đầu và kết thúc bằng mũi tên ( =>).

Tôi cũng sẽ thêm rằng các thành viên thân thể biểu hiện không giới hạn đối với các thành viên tài sản. Họ làm việc trên tất cả các thành viên này:

  • Tính chất
  • Người lập chỉ mục
  • Phương pháp
  • Người vận hành

Đã thêm vào C # 7.0

Tuy nhiên, họ không làm việc trên các thành viên này:

  • Các loại lồng nhau
  • Sự kiện
  • Lĩnh vực

6
Kể từ C # 7, các nhà xây dựng và hoàn thiện cũng được hỗ trợ. docs.microsoft.com/en-us/dotnet/csharp/programming-guide/ mẹo
bzier

8
@bzier Đó là một âm mưu để làm cho chúng tôi lập trình viên chức năng. NẾU SAU mãi mãi !!
Sentinel

Câu trả lời siêu tuyệt vời!
Jaime Arroyo Garcia

2
Liên kết đến bài đăng của Bill Wagner hiện đang bị hỏng. Tôi nghĩ rằng tôi đã tìm thấy url mới: codeproject.com/Articles/1064964/ Khăn
Fry Simpson

36

Ok ... Tôi đã nhận xét rằng họ khác nhau nhưng không thể giải thích chính xác bằng cách nào nhưng bây giờ tôi biết.

String Property { get; } = "value";

không giống như

String Property => "value";

Đây là sự khác biệt ...

Khi bạn sử dụng trình khởi tạo tự động, thuộc tính sẽ tạo ra thể hiện của giá trị và sử dụng giá trị đó một cách liên tục. Trong bài viết trên có một liên kết bị hỏng với Bill Wagner, điều đó giải thích điều này rất tốt và tôi đã tìm kiếm liên kết chính xác để tự hiểu nó.

Trong tình huống của tôi, tôi đã tự động khởi tạo một lệnh trong ViewModel cho View. Tôi đã thay đổi thuộc tính để sử dụng trình khởi tạo thân biểu thức và lệnh CanExecute ngừng hoạt động.

Đây là những gì nó trông giống như và đây là những gì đang xảy ra.

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

đây là những gì tôi đã thay đổi nó thành.

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

Sự khác biệt ở đây là khi tôi sử dụng { get; } =tôi tạo và tham chiếu lệnh SAME trong thuộc tính đó. Khi tôi sử dụng, =>tôi thực sự tạo một lệnh mới và trả lại mỗi khi thuộc tính được gọi. Do đó, tôi không bao giờ có thể cập nhật CanExecutelệnh trên vì tôi luôn bảo nó cập nhật một tham chiếu mới của lệnh đó.

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

Tất cả những gì đã nói, nếu bạn chỉ trỏ đến một trường sao lưu thì nó hoạt động tốt. Điều này chỉ xảy ra khi cơ thể tự động hoặc biểu thức tạo giá trị trả về.


8
Cú pháp => bằng với get {return new Command (); } cú pháp.
Mafii

35

Đây là một tính năng mới của C # 6 được gọi là thành viên thân biểu thức cho phép bạn xác định thuộc tính chỉ getter bằng cách sử dụng hàm lambda.

Mặc dù nó được coi là đường cú pháp cho những điều sau đây, nhưng chúng có thể không tạo ra IL giống hệt nhau:

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

Hóa ra nếu bạn biên dịch cả hai phiên bản trên và so sánh IL được tạo cho từng phiên bản, bạn sẽ thấy rằng chúng KHÔNG CẦN giống nhau.

Đây là IL cho phiên bản cổ điển trong câu trả lời này khi được định nghĩa trong một lớp có tên 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

Và đây là IL cho phiên bản thành viên thân thể biểu thức khi được định nghĩa trong một lớp có tên 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

Xem https://msdn.microsoft.com/en-us/magazine/dn802602.aspx để biết thêm thông tin về điều này và các tính năng mới khác trong C # 6.

Xem bài đăng này Sự khác biệt giữa Thuộc tính và Trường trong C # 3.0+ về sự khác biệt giữa trường và trình nhận thuộc tính trong C #.

Cập nhật:

Lưu ý rằng các thành viên thân biểu thức đã được mở rộng để bao gồm các thuộc tính, hàm tạo, bộ hoàn thiện và bộ chỉ mục trong C # 7.0.


16

Nó được gọi là Thành viên thân thể biểu hiện và nó được giới thiệu trong C # 6. Nó chỉ là đường cú pháp trên một gettài sản duy nhất.

Nó tương đương với:

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

Tương đương với khai báo phương thức là có sẵn:

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

Chủ yếu cho phép bạn rút ngắn nồi hơi.


7

Một điểm quan trọng khác nếu bạn đang sử dụng C # 6:

'=>' có thể được sử dụng thay vì 'get' và chỉ dành cho các phương thức 'get only' - nó không thể được sử dụng với 'set'.

Đối với C # 7, hãy xem nhận xét từ @avenmore bên dưới - giờ đây có thể được sử dụng ở nhiều nơi hơn. Đây là một tài liệu tham khảo tốt - https://csharp.christiannagel.com/2017/01/11/expressionbodiedmembers/


8
Không còn đúng nếu bạn đang sử dụng C # 7. "C # 7.0 tiếp tục với các cải tiến năng suất. Các thành viên thân thể đã có sẵn với C # 6 cho các phương thức và thuộc tính, giờ đây chúng có thể được sử dụng với các hàm tạo, hàm hủy, bộ truy cập thuộc tính và bộ truy cập sự kiện cũng." ( Nguồn )
avenmore

1

Đối với tuyên bố sau đây được chia sẻ bởi Alex Booker trong câu trả lời của họ

Khi trình biên dịch gặp một thành viên thuộc tính biểu thức, về cơ bản nó sẽ chuyển đổi nó thành một getter như thế này:

Vui lòng xem ảnh chụp màn hình sau , nó cho thấy cách tuyên bố này (sử dụng liên kết SharpLab )

public string APIBasePath => Configuration.ToolsAPIBasePath;

chuyển đổi thành

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

Ảnh chụp màn hình: nhập mô tả hình ảnh ở đây

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.