2009-01-14 33 views
26

Lỗi biên dịch CS0283 cho biết chỉ các loại POD cơ bản (cũng như các chuỗi, enums và tham chiếu null) có thể được khai báo là const. Có ai có một lý thuyết về lý do cho giới hạn này? Ví dụ, nó sẽ được tốt đẹp để có thể khai báo giá trị const của các loại khác, chẳng hạn như IntPtr.Tại sao C# giới hạn tập các loại có thể được khai báo là const?

Tôi tin rằng khái niệm về const thực sự là cú pháp đường trong C# và nó chỉ thay thế bất kỳ sử dụng nào của tên có giá trị bằng chữ. Ví dụ, với tuyên bố sau đây, bất kỳ tham chiếu đến Foo sẽ được thay thế bằng "foo" tại thời gian biên dịch.

const string Foo = "foo"; 

Điều này sẽ loại trừ mọi loại có thể thay đổi, vì vậy có thể họ đã chọn giới hạn này thay vì phải xác định thời gian biên dịch cho dù loại đã cho có thể thay đổi không?

Trả lời

26

Từ C# specification, chapter 10.4 - Constants:
(10.4 trong đặc tả C# 3.0, 10.3 trong phiên bản trực tuyến với 2,0)

Một hằng là thành viên lớp đại diện cho một giá trị không đổi: một giá trị mà có thể được tính toán tại thời gian biên dịch.

Điều này về cơ bản nói rằng bạn chỉ có thể sử dụng các cụm từ chỉ bao gồm các chữ. Bất kỳ lời gọi nào đến bất kỳ phương thức nào, các hàm tạo (không thể được biểu diễn dưới dạng các chữ IL thuần túy) đều không thể được sử dụng, vì không có cách nào để trình biên dịch thực hiện điều đó và do đó tính toán kết quả, tại thời gian biên dịch. Ngoài ra, vì không có cách nào để gắn thẻ một phương thức là bất biến (ví dụ:có một ánh xạ một-một giữa đầu vào và đầu ra), cách duy nhất để trình biên dịch thực hiện điều này là phân tích IL để xem nó có phụ thuộc vào các thứ khác ngoài các tham số đầu vào, trường hợp đặc biệt xử lý một số loại (như IntPtr), hoặc không cho phép mọi cuộc gọi đến bất kỳ mã nào.

IntPtr, ví dụ, mặc dù là một loại giá trị, vẫn là một cấu trúc chứ không phải một trong các chữ cái dựng sẵn. Như vậy, bất kỳ biểu thức nào sử dụng IntPtr sẽ cần phải gọi mã trong cấu trúc IntPtr, và đây là điều không hợp pháp đối với một khai báo liên tục.

Ví dụ loại giá trị hằng số hợp pháp duy nhất mà tôi có thể nghĩ là một ví dụ được khởi tạo bằng số 0 bằng cách chỉ khai báo nó và điều đó hầu như không hữu ích.

Đối với cách trình biên dịch xử lý/sử dụng hằng số, nó sẽ sử dụng giá trị được tính thay cho tên không đổi trong mã.

Vì vậy, bạn có hiệu lực thi hành sau:

  • Không tham chiếu đến tên liên tục ban đầu, lớp nó được khai báo trong, hoặc không gian tên, được biên soạn vào mã ở vị trí
  • này Nếu bạn biên soạn lại các mã, nó sẽ có số ma thuật trong đó, đơn giản vì "tham chiếu" ban đầu cho hằng số, như đã đề cập ở trên, không có, chỉ giá trị của hằng số
  • Trình biên dịch có thể sử dụng để tối ưu hóa hoặc thậm chí loại bỏ, mã không cần thiết. Ví dụ, if (SomeClass.Version == 1), khi SomeClass.Version có giá trị là 1, trên thực tế sẽ loại bỏ câu lệnh if và giữ cho khối mã đang được thực thi. Nếu giá trị của hằng số không phải là 1, thì toàn bộ if-statement và khối của nó sẽ bị loại bỏ.
  • Vì giá trị của hằng số được biên dịch vào mã và không tham chiếu đến hằng số, sử dụng hằng số từ các assembly khác sẽ không tự động cập nhật mã đã biên dịch theo bất kỳ cách nào nếu giá trị của hằng số thay đổi. không)

Nói cách khác, với các tình huống sau:

  1. hội A, chứa một hằng số có tên là "phiên bản", có giá trị là 1
  2. hội B, chứa một biểu thức phân tích số phiên bản của assembly A từ hằng số đó nd so sánh nó với 1, để đảm bảo nó có thể làm việc với các lắp ráp
  3. Có người sẽ thay đổi lắp ráp A, tăng giá trị của hằng số 2, và tái thiết A (nhưng không phải B)

Trong trường hợp này, lắp ráp B, ở dạng biên dịch của nó, sẽ vẫn so sánh giá trị từ 1 đến 1, bởi vì khi B được biên dịch, hằng số có giá trị 1.

Thực tế, nếu đó là cách sử dụng duy nhất của bất kỳ thứ gì từ lắp ráp A lắp ráp B, lắp ráp B sẽ được biên dịch mà không phụ thuộc vào lắp ráp A. Thực thi mã có chứa biểu thức đó trong lắp ráp B sẽ không tải lắp ráp A.

Hằng số do đó chỉ được sử dụng cho những thứ sẽ không bao giờ thay đổi. Nếu nó là một giá trị có thể hoặc sẽ thay đổi một thời gian trong tương lai, và bạn không thể đảm bảo rằng tất cả các hội đồng khác được xây dựng lại đồng thời, một trường chỉ đọc thích hợp hơn một hằng số.

Vì vậy, đây là ok:

  • const công Int32 NumberOfDaysInAWeekInGregorianCalendar = 7;
  • công khai const Int32 NumberOfHoursInADayOnEarth = 24;

trong khi đây không phải là:

  • const công Int32 AgeOfProgrammer = 25;
  • chuỗi công khai const NameOfLastProgrammerThatModifiedAssembly = "Joe Programmer";

Chỉnh sửa ngày 27 tháng 5 2016

OK, chỉ có một phiếu bầu tán thành, vì vậy tôi đọc lại câu trả lời của tôi ở đây và điều này thực sự là một chút sai.

Bây giờ, ý định đặc tả ngôn ngữ C# là mọi thứ tôi đã viết ở trên. Bạn không được phép sử dụng cái gì đó không thể được đại diện với một chữ như là một const.

Nhưng bạn có thể? Vâng, vâng ....

Hãy xem loại decimal.

public class Test 
{ 
    public const decimal Value = 10.123M; 
} 

Hãy nhìn vào những gì lớp này trông giống như thực sự khi nhìn với ildasm:

.field public static initonly valuetype [mscorlib]System.Decimal X 
.custom instance void [mscorlib]System.Runtime.CompilerServices.DecimalConstantAttribute::.ctor(int8, uint8, uint32, uint32, uint32) = (01 00 01 00 00 00 00 00 00 00 00 00 64 00 00 00 00 00) 

Hãy để tôi phá vỡ nó xuống cho bạn:

.field public static initonly 

tương ứng với:

public static readonly 

Đúng vậy, const decimal thực sự là một readonly decimal.

Thỏa thuận thực sự ở đây là trình biên dịch sẽ sử dụng DecimalConstantAttribute để làm việc phép thuật của nó.

Bây giờ, đây là phép thuật duy nhất tôi biết với trình biên dịch C# nhưng tôi nghĩ nó đáng nói đến.

+1

Rõ ràng là bạn không thể làm điều này nhưng * giả thuyết nói *, nói rằng chúng ta có một phương thức tĩnh kiểu giá trị trả về đã được đánh dấu bằng thuộc tính [Pure] và trình biên dịch thực sự thực thi nó (nó không phải hiện tại). Có thể các nhà thiết kế ngôn ngữ ít nhất * về mặt lý thuyết * cho phép một const được khởi tạo với phương thức thuần túy kiểu trả về tĩnh (với thực thi thuộc tính [Pure] thực tế) miễn là các đối số không là gì ngoài các chữ? Có vẻ như họ chỉ có thể chạy nó với các args đen, có được kết quả rõ ràng là sẽ không thay đổi và phụ trong giá trị. Tôi có nhìn cái gì không? –

+0

'Trong trường hợp này, assembly B, ở dạng được biên dịch của nó, sẽ vẫn so sánh giá trị 1 đến 1, bởi vì khi B được biên dịch, hằng số có giá trị 1' - bạn vẫn có thể làm điều tương tự với' public const int AssemblyVersion = 1' trong bất kỳ phiên bản C# nào ngày hôm nay, vì vậy không có gì sai với * cấu trúc * mình –

1

Tôi tin rằng khái niệm về const thực sự là cú pháp đường trong C#, và rằng nó chỉ thay thế bất kỳ mục đích sử dụng của tên với giá trị văn chương

gì trình biên dịch làm với các đối tượng const trong khác ngôn ngữ?

Bạn có thể sử dụng chỉ đọc cho các loại có thể thay đổi mà tôi được đánh giá khi chạy. Xem this article để biết sự khác biệt.

+0

Tôi đoán bạn có thể kiểm tra điều này bằng cách sử dụng Reflector. – BuddyJoe

+0

Từ khóa const có thể có các hiệu ứng khác nhau trong các ngôn ngữ khác nhau, như trong C++, nơi nó có thể được sử dụng để đảm bảo rằng một phương thức không thay đổi trạng thái của một đối tượng, làm ví dụ. –

+0

@ LasseV.Karlsen, nhưng 'const' có nghĩa là điều tương tự trong cả C (++) và C# đối với các loại * không tham chiếu *. Vì vậy, câu trả lời này là đúng khi nói về điều đó. Các ngôn ngữ khác có khái niệm "const đúng đắn". Tương tự gần nhất trong C# là mẫu bất biến được kết hợp với các thành viên lớp 'readonly' được khởi tạo bởi hàm tạo. – binki

0

consts được giới hạn số và chuỗi trong C# vì trình biên dịch thay thế biến bằng giá trị chữ trong MSIL. Nói cách khác khi bạn viết:

const string myName = "Bruce Wayne"; 
if (someVar == myName) 
{ 
    ... 
} 

được thực sự coi là

if (someVar == "Bruce Wayne") 
{ 
    ... 
} 

và có, biên dịch C# là đủ thông minh để đối xử với các nhà điều hành bình đẳng (==) trên dây như

string1.Equals(string2) 
+0

Đúng, điều tôi không hiểu là lý do bạn không thể khai báo, ví dụ: một IntPtr const, cũng có thể được thay thế tại các điểm sử dụng. Có lẽ sự khác biệt là một IntPtr sẽ phải được xây dựng, trong khi POD và các chuỗi có thể được biểu diễn trực tiếp trong IL. – Charlie

+0

Trình biên dịch sẽ phải thực thi mã trong IntPtr để nhận giá trị cuối cùng và phần "thực thi mã để lấy giá trị" này phải được biên dịch vào mã bất cứ nơi nào hằng số được sử dụng, và đây là phần không được phép . –

1

Có ai có lý thuyết về cơ sở lý luận cho giới hạn này không?

Nếu nó được phép chỉ là một lý thuyết, lý thuyết của tôi là giá trị const của các kiểu dữ liệu có thể được thể hiện ở các thông số opcode chữ trong MSIL ... nhưng các giá trị của các loại khác, phi nguyên thủy có thể không, bởi vì MSIL không có cú pháp để biểu thị giá trị của một kiểu do người dùng định nghĩa như một chữ.

+0

Điều đó dường như có thể, vâng (xem thêm chú thích của tôi cho @IAmCodeMonkey) – Charlie

+0

MSIL định nghĩa các hằng số theo thứ tự các byte. Cho một loại như ví dụ: 'struct Vector2d {public double X, Y;}', một trình biên dịch có thể cho phép khai báo hằng số 'const Vector2d UnitXVector = {X = 1.0, Y = 0.0};', vì nó có thể xác định chuỗi chính xác các byte được biểu diễn bởi kết cấu. Điều đó sẽ chỉ hoạt động, tuy nhiên, đối với một cấu trúc không có trường riêng; nếu một cấu trúc sử dụng các thuộc tính công cộng và các trường sao lưu riêng, thì trình biên dịch chỉ có thể xây dựng nó bằng cách tìm ra chính xác các thuộc tính đã làm. – supercat

+0

@supercat, bạn ngụ ý rằng MSIL có thể dựa vào các đối tượng đang được đặt ra một cách nào đó trong bộ nhớ? Không tồn tại ['StructLayoutAttribute'] (https://msdn.microsoft.com/en-us/library/System.Runtime.InteropServices.StructLayoutAttribute%28v=vs.100%29.aspx) ngụ ý rằng nó có thể ' t? – binki

0

Dường như với tôi rằng các kiểu giá trị duy nhất có thể được biểu diễn dưới dạng hằng số (ngoại trừ các chuỗi, đứng ở đâu đó giữa giá trị và kiểu đối tượng). Nó là OK cho tôi: đối tượng (tài liệu tham khảo) phải được phân bổ trên heap nhưng hằng số không được phân bổ ở tất cả (vì chúng được thay thế tại thời gian biên dịch).

+1

Chuỗi có thể được sử dụng vì trình biên dịch có thể chèn mã IL để tải một chuỗi có giá trị nhất định. Vì đây là phép thuật IL đặc biệt được thêm vào cho chuỗi, chúng có thể được sử dụng. –

0

Tóm lại, tất cả các loại đơn giản, enums và chuỗi là bất biến nhưng một ví dụ là không. Bạn có thể có một cấu trúc với trạng thái có thể thay đổi (các trường, thuộc tính, thậm chí tham chiếu đến các kiểu tham chiếu). Vì vậy, trình biên dịch không thể đảm bảo (tại thời gian biên dịch) rằng trạng thái bên trong của một biến Struct không thể thay đổi.Vì vậy, trình biên dịch cần phải chắc chắn rằng một loại là theo định nghĩa không thay đổi được sử dụng trong một biểu thức liên tục.

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