2016-10-03 14 views
10

Trước C# 6, việc khởi tạo thuộc tính không sử dụng trường sao lưu để khởi tạo giá trị mặc định. Trong C# 6, nó sử dụng các trường sao lưu để khởi tạo với Auto initialization properties mới.C# 6 Tự động khởi tạo Bất động sản và sử dụng các trường sao lưu

Tôi tò mò tại sao trước khi C# 6 IL sử dụng định nghĩa thuộc tính để khởi tạo. Có lý do cụ thể nào cho điều này không? hoặc là nó không được triển khai đúng trước C# 6?

Trước khi C# 6,0

public class PropertyInitialization 
{ 
    public string First { get; set; } 

    public string Last { get; set; } 

    public PropertyInitialization() 
    { 
     this.First = "Adam"; 
     this.Last = "Smith"; 
    } 
} 

trình biên dịch tạo Mã (IL đại diện)

public class PropertyInitialisation 
    { 
    [CompilerGenerated] 
    private string \u003CFirst\u003Ek__BackingField; 
    [CompilerGenerated] 
    private string \u003CLast\u003Ek__BackingField; 

    public string First 
    { 
     get 
     { 
     return this.\u003CFirst\u003Ek__BackingField; 
     } 
     set 
     { 
     this.\u003CFirst\u003Ek__BackingField = value; 
     } 
    } 

    public string Last 
    { 
     get 
     { 
     return this.\u003CLast\u003Ek__BackingField; 
     } 
     set 
     { 
     this.\u003CLast\u003Ek__BackingField = value; 
     } 
    } 

    public PropertyInitialisation() 
    { 
     base.\u002Ector(); 
     this.First = "Adam"; 
     this.Last = "Smith"; 
    } 
    } 

C# 6

public class AutoPropertyInitialization 
{ 
    public string First { get; set; } = "Adam"; 
    public string Last { get; set; } = "Smith"; 
} 

trình biên dịch tạo Mã (IL đại diện)

public class AutoPropertyInitialization 
    { 
    [CompilerGenerated] 
    [DebuggerBrowsable(DebuggerBrowsableState.Never)] 
    private string \u003CFirst\u003Ek__BackingField; 
    [CompilerGenerated] 
    [DebuggerBrowsable(DebuggerBrowsableState.Never)] 
    private string \u003CLast\u003Ek__BackingField; 

    public string First 
    { 
     get 
     { 
     return this.\u003CFirst\u003Ek__BackingField; 
     } 
     set 
     { 
     this.\u003CFirst\u003Ek__BackingField = value; 
     } 
    } 

    public string Last 
    { 
     get 
     { 
     return this.\u003CLast\u003Ek__BackingField; 
     } 
     set 
     { 
     this.\u003CLast\u003Ek__BackingField = value; 
     } 
    } 

    public AutoPropertyInitialization() 
    { 
     this.\u003CFirst\u003Ek__BackingField = "Adam"; 
     this.\u003CLast\u003Ek__BackingField = "Smith"; 
     base.\u002Ector(); 
    } 
    } 
+3

Bạn có thể cho chúng tôi thấy mã C# 5 và/hoặc 6 dẫn đến IL này không? Câu đầu tiên của bạn gây nhầm lẫn cho tôi vì bạn không thể tự động khởi tạo thuộc tính trước C# 6 - bạn phải thực hiện nó trong một hàm tạo theo cách thủ công. –

+0

Fileds phải được chỉ định trước khi ctor với khởi tạo autoproperty (spec nói như vậy). Và trước khi ctor - không có trường sao lưu bất động sản được khởi tạo đúng -> điều này đang được thực hiện trong ctor -> sử dụng các thuộc tính trực tiếp sẽ gây ra các đơn lẻ được sử dụng – Mafii

+1

Bạn phải hiển thị toàn bộ đoạn mã, về cơ bản hiển thị toàn bộ lớp vì nó đã ở trong C# 5 và vì nó nằm trong C# 6, nếu không chúng ta chỉ có thể đoán được lý do tại sao trình biên dịch hoạt động như nó có vẻ cho bạn. –

Trả lời

6

Tôi tò mò tại sao trước khi C# 6 IL sử dụng định nghĩa thuộc tính để khởi tạo. Có lý do cụ thể nào cho điều này không?

Bởi vì thiết lập một giá trị thông qua khởi tự động tài sản và thiết lập các giá trị trong một constructor là hai thứ khác nhau. Họ có những hành vi khác nhau.

Nhớ lại thuộc tính đó là các phương thức truy cập bao quanh các trường. Vì vậy, dòng này:

this.First = "Adam"; 

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

this.set_First("Adam"); 

Bạn thậm chí có thể thấy điều này trong Visual Studio! Hãy thử viết một phương thức có chữ ký public string set_First(string value) trong lớp học của bạn và xem khi trình biên dịch phàn nàn về bạn bước lên các ngón chân của nó.

Và cũng giống như các phương pháp, chúng có thể bị ghi đè trong các lớp con. Hãy xem mã này:

public class PropertyInitialization 
{ 
    public virtual string First { get; set; } 

    public PropertyInitialization() 
    { 
     this.First = "Adam"; 
    } 
} 

public class ZopertyInitalization : PropertyInitialization 
{ 
    public override string First 
    { 
     get { return base.First; } 
     set 
     { 
      Console.WriteLine($"Child property hit with the value: '{0}'"); 
      base.First = value; 
     } 
    } 
} 

Trong ví dụ này, dòng this.First = "Adam" sẽ gọi setter trong lớp con. Bởi vì bạn đang gọi một phương pháp, hãy nhớ? Nếu trình biên dịch là để giải thích phương thức này gọi như là một cuộc gọi trực tiếp đến trường sao lưu, nó sẽ không kết thúc lên gọi setter con. Hành vi biên dịch mã của bạn sẽ thay đổi hành vi của chương trình của bạn. Không tốt!

Thuộc tính tự động khác nhau. Cho phép thay đổi ví dụ đầu tiên bằng cách sử dụng trình khởi tạo thuộc tính tự động:

public class PropertyInitialization 
{ 
    public virtual string First { get; set; } = "Adam"; 
} 

public class ZopertyInitalization : PropertyInitialization 
{ 
    public override string First 
    { 
     get { return base.First; } 
     set 
     { 
      Console.WriteLine($"Child property hit with the value: '{0}'"); 
      base.First = value; 
     } 
    } 
} 

Với mã này, phương thức setter trong lớp con sẽ không được gọi. Đây là cố ý. Trình khởi tạo thuộc tính tự động được thiết kế để đặt trường sao lưu trực tiếp. Họ trông và hành xử như initializers lĩnh vực, đó là lý do chúng tôi thậm chí có thể sử dụng chúng trên các thuộc tính mà không setters, như thế này:

public string First { get; } = "Adam"; 

Không có phương pháp setter đây! Chúng tôi sẽ phải trực tiếp truy cập vào lĩnh vực sao lưu để làm điều này. Thuộc tính tự động cho phép các lập trình viên tạo ra các giá trị bất biến trong khi vẫn có thể hưởng lợi từ cú pháp đẹp.

+0

có vẻ như câu trả lời của bạn phù hợp/hoàn chỉnh hơn và giải thích sự khác biệt chính xác. nghĩa là "Nếu trình biên dịch giải thích phương thức này gọi như một cuộc gọi trực tiếp đến trường sao lưu, nó sẽ không kết thúc việc gọi trình setter con." Một số câu trả lời khác cũng chính xác, nhưng có thể hơi phức tạp/không trực tiếp, hoặc bit off chủ đề. Cảm ơn! – Spock

0

tôi giả sử mã C# 5.0 của bạn trông như thế này:

class C 
{ 
    public C() 
    { 
     First = "Adam"; 
    } 

    public string First { get; private set; } 
} 

Và sau đó trong C# 6.0, sự thay đổi duy nhất bạn làm là làm cho First một get autoproperty -only:

class C 
{ 
    public C() 
    { 
     First = "Adam"; 
    } 

    public string First { get; } 
} 

Trong trường hợp C# 5.0, First là một chỗ dựa erty với một setter và bạn sử dụng nó trong constructor, do đó, IL tạo ra phản ánh điều đó.

Trong phiên bản C# 6.0, First không có setter, vì vậy hàm tạo phải truy cập trực tiếp trường sao lưu.

Cả hai trường hợp đều có ý nghĩa hoàn hảo với tôi.

+2

Tôi nghĩ rằng đó là về 'chuỗi công khai Đầu tiên {get; bộ tư nhân; } = "Adam"; ', nơi setter tồn tại. – hvd

+0

@hvd Nhưng mã C# 5.0 trông như thế nào? – svick

+2

'chuỗi công khai Đầu tiên {get; bộ tư nhân; } ', và sau đó trong hàm tạo,' First = "Adam"; 'Nói cách khác, chính xác những gì bạn nghĩ mã C# 5.0 trông như thế nào. – hvd

1

Thời gian duy nhất nó tạo sự khác biệt là nếu trình thiết lập thuộc tính có nhiều hiệu ứng hơn là chỉ cần đặt giá trị. Đối với các thuộc tính được tự động triển khai, thời gian duy nhất có thể xảy ra là nếu chúng là virtual và bị ghi đè. Trong trường hợp đó, gọi phương thức lớp dẫn xuất trước khi hàm tạo lớp cơ sở đã chạy là một ý tưởng rất tồi. C# đi qua rất nhiều rắc rối để đảm bảo rằng bạn không vô tình kết thúc với các tham chiếu đến các đối tượng chưa được khởi tạo đầy đủ. Vì vậy, nó phải thiết lập các lĩnh vực trực tiếp để ngăn chặn điều đó.

+1

Có một điểm khác: Giá trị của các thuộc tính được khởi tạo có thể bị ghi đè bởi các giá trị được đặt trong hàm tạo. Điều này có nghĩa là các giá trị mặc định phải được đặt trước hàm tạo (để đảm bảo điều này xảy ra). Nhưng trước khi gọi hàm tạo, các giá trị và phương thức chưa được khởi tạo (bao gồm cả các bộ định tuyến). Đó là lý do tại sao họ phải truy cập trực tiếp vào các trường. – Mafii

+1

@Mafii IIRC, trước khi gọi hàm dựng cơ sở, tất cả các phương thức đều có thể gọi được, thường chỉ là một ý tưởng rất xấu để sử dụng điều này: đặc tả CIL chỉ nói rằng các phương thức gọi trước khi lời gọi hàm tạo cơ sở có nghĩa là mã không thể xác minh được, và mã chưa xác minh vẫn có thể chính xác. – hvd

1

Hãy nhớ rằng các giá trị được đặt làm mặc định cho thuộc tính không được đặt trong hàm tạo (mã của bạn cho thấy: assigments, sau đó hàm tạo).

Bây giờ, thông số C# nói rằng giá trị tự khởi tạo được đặt trước hàm tạo. Điều này có ý nghĩa: Khi các giá trị này được đặt lại trong hàm tạo, chúng bị ghi đè.

Bây giờ - trước khi hàm tạo được gọi - không có phương thức getter và setter được khởi tạo. Chúng nên được sử dụng như thế nào?

Thats lý do tại sao (sau đó uninitialized sao lưu trường) đang được khởi tạo trực tiếp.

Như đã đề cập ở trên, cũng có vấn đề với cuộc gọi ảo, nhưng chúng thậm chí không được khởi tạo là lý do chính.


Nó vẫn hành xử theo cùng một cách như trước đây nếu bạn gán giá trị trong các nhà xây dựng:

Example with property that is autoinitialized and changed in the ctor

Tại sao không này được tối ưu hóa ra?

Xem my question về chủ đề này:

Nhưng không nên nó tối ưu hóa mà ra?

Nó có lẽ có thể, nhưng chỉ khi lớp mà không kế thừa từ một lớp sử dụng giá trị đó trong constructor của nó, nó biết rằng nó là một tự động tài sản và setter không làm bất cứ điều gì khác .

Đó sẽ là rất nhiều giả định (nguy hiểm). Trình biên dịch cần phải kiểm tra nhiều thứ trước khi thực hiện tối ưu hóa như vậy.


Side lưu ý:

Tôi giả sử bạn sử dụng một số công cụ cho thấy trình biên dịch tạo ra C# mã - nó không hoàn toàn chính xác. Không có biểu thức chính xác nào cho mã IL đang được tạo ra cho một hàm tạo - ctor không phải là một phương thức trong IL, nó là một cái gì đó khác. Vì lợi ích của sự hiểu biết chúng ta có thể giả định nó là cùng một tho.

http://tryroslyn.azurewebsites.net/ làm ví dụ có nhận xét này:

// This is not valid C#, but it represents the IL correctly. 
+0

Đây dường như là câu trả lời trực tiếp nhất cho câu hỏi và do đó IMO cũng là câu trả lời được chấp nhận. – Bauss

+1

@Mafi có trình biên dịch tạo mã từ JetBrains Dot Peek. – Spock

1

Một cách để bạn có thể lấy mã như là bạn có bạn C# 5 mã như thế này:

public class Test : Base 
{ 
    public Test() 
    { 
     A = "test"; 
    } 

    public string A { get; set; } 
} 

này sẽ sản xuất (IL) như sau:

public Test..ctor() 
{ 
    Base..ctor(); 
    A = "test"; 
} 

Mã C# 6 của bạn sẽ giống như sau:

public class Test : Base 
{ 
    public Test() 
    { 
    } 

    public string A { get; set; } = "test"; 
} 

nào sản xuất (IL) mã như thế này:

public Test..ctor() 
{ 
    <A>k__BackingField = "test"; 
    Base..ctor(); 
} 

Lưu ý rằng nếu bạn khởi tạo tài sản của bạn đặc biệt là trong các nhà xây dựng, và có một getter/setter property, trong C# 6 nó sẽ vẫn tìm kiếm như phần đầu tiên của mã trong câu trả lời của tôi ở trên, trong khi nếu bạn có một getter chỉ trường nó sẽ giống như thế này:

public Test..ctor() 
{ 
    Base..ctor(); 
    <A>k__BackingField = "test"; 
} 

Vì vậy, nó là khá rõ ràng, bạn C# 5 mã trông giống như phần đầu tiên của mã ở trên, và mã C# 6 của bạn trông giống như đoạn mã thứ hai.

Vì vậy, để trả lời câu hỏi của bạn: Tại sao C# 5 và C# 6 hoạt động khác nhau về cách nó biên dịch khởi tạo thuộc tính tự động?Lý do là vì bạn không thể khởi tạo thuộc tính tự động trong C# 5 hoặc trước đó và các mã khác nhau biên dịch khác nhau.

Các vấn đề liên quan