Những lợi ích để đánh dấu một trường là `readonly` trong C # là gì?


295

Những lợi ích của việc có một biến thành viên được khai báo là chỉ đọc là gì? Có phải nó chỉ bảo vệ chống lại ai đó thay đổi giá trị của nó trong vòng đời của lớp hoặc sử dụng từ khóa này dẫn đến bất kỳ cải thiện tốc độ hoặc hiệu quả nào?


8
Câu trả lời hay bên ngoài: dotnetperls.com/readonly
OneWorld

3
Hấp dẫn. Đây thực chất là tương đương C # của stackoverflow câu hỏi Java này / câu hỏi / 371868 / Cuộc thảo luận ở đây ít nóng hơn mặc dù ... hmm ...
RAY

7
Có thể đáng lưu ý rằng readonlycác trường của các loại cấu trúc áp dụng hình phạt hiệu năng so với các trường có thể thay đổi đơn giản là không bị đột biến, vì việc gọi bất kỳ thành viên nào của readonlytrường loại giá trị sẽ khiến trình biên dịch tạo một bản sao của trường và gọi thành viên trên đó.
supercat

2
thêm về hình phạt hiệu suất: codeblog.jonskeet.uk/2014/07/16/ mẹo
CAD bloke

Câu trả lời:


171

Các readonlytừ khóa được sử dụng để khai báo một biến thành viên một hằng số, nhưng cho phép các giá trị được tính tại thời gian chạy. Điều này khác với một hằng số được khai báo với bộ constsửa đổi, phải đặt giá trị của nó tại thời gian biên dịch. Sử dụng readonlybạn có thể đặt giá trị của trường trong khai báo hoặc trong hàm tạo của đối tượng mà trường là thành viên của.

Cũng sử dụng nó nếu bạn không muốn phải biên dịch lại các DLL bên ngoài tham chiếu hằng số (vì nó được thay thế tại thời gian biên dịch).


193

Tôi không tin có bất kỳ hiệu suất nào đạt được từ việc sử dụng trường chỉ đọc. Nó chỉ đơn giản là một kiểm tra để đảm bảo rằng một khi đối tượng được xây dựng đầy đủ, trường đó không thể được trỏ đến một giá trị mới.

Tuy nhiên "chỉ đọc" rất khác so với các loại ngữ nghĩa chỉ đọc khác bởi vì nó được thi hành trong thời gian chạy bởi CLR. Từ khóa chỉ đọc biên dịch thành .initonly có thể kiểm chứng bằng CLR.

Ưu điểm thực sự của từ khóa này là tạo ra các cấu trúc dữ liệu bất biến. Cấu trúc dữ liệu bất biến theo định nghĩa không thể thay đổi một khi được xây dựng. Điều này làm cho nó rất dễ dàng để lý giải về hành vi của một cấu trúc trong thời gian chạy. Chẳng hạn, không có nguy cơ chuyển một cấu trúc bất biến sang một phần mã ngẫu nhiên khác. Họ không thể thay đổi nó bao giờ để bạn có thể lập trình đáng tin cậy theo cấu trúc đó.

Đây là một mục tốt về một trong những lợi ích của tính bất biến: Threading


6
Nếu bạn đọc điều này, stackoverflow.com/questions/9860595/ Thành viên chỉ đọc có thể được sửa đổi và trông giống như một hành vi không nhất quán của .net
Akash Kava

Bạn có thể vui lòng cập nhật liên kết đến bài viết trên Threading ở trên không? Nó bị hỏng.
Akshay Khot

69

Không có lợi ích hiệu suất rõ ràng khi sử dụng readonly, ít nhất là không có lợi ích nào tôi từng thấy được đề cập ở bất cứ đâu. Nó chỉ để thực hiện chính xác như bạn đề xuất, để ngăn chặn sửa đổi một khi nó đã được khởi tạo.

Vì vậy, nó có lợi ở chỗ nó giúp bạn viết mã mạnh mẽ hơn, dễ đọc hơn. Lợi ích thực sự của những thứ như thế này đến khi bạn làm việc trong nhóm hoặc để bảo trì. Tuyên bố một cái gì đó readonlygiống như đặt một hợp đồng cho việc sử dụng biến đó trong mã. Hãy nghĩ về nó như thêm tài liệu theo cách tương tự như các từ khóa khác như internalhoặc private, bạn đang nói "biến này không nên được sửa đổi sau khi khởi tạo", và hơn nữa bạn đang thực thi nó.

Vì vậy, nếu bạn tạo một lớp và đánh dấu một số biến thành viên readonlytheo thiết kế, thì bạn sẽ ngăn mình hoặc một thành viên khác trong nhóm mắc lỗi sau này khi họ mở rộng hoặc sửa đổi lớp của bạn. Theo tôi, đó là một lợi ích đáng có (với chi phí nhỏ của sự phức tạp ngôn ngữ thêm như doofledorfer đề cập trong các bình luận).


Và otoh mô tả ngôn ngữ. Không phủ nhận tuyên bố lợi ích của bạn, tuy nhiên.
dkretz

3
Tôi đồng ý, nhưng tôi cho rằng lợi ích thực sự đến khi có nhiều hơn một người đang làm việc với mã. Nó giống như có một tuyên bố thiết kế nhỏ trong mã, một hợp đồng cho việc sử dụng nó. Tôi có lẽ nên đặt nó trong câu trả lời, hehe.
Xiaofu

7
Câu trả lời và thảo luận này thực sự là câu trả lời tốt nhất theo ý kiến ​​của tôi +1
Jeff Martin

@Xiaofu: Bạn khiến tôi liên tục nảy ra ý tưởng về sự giải thích đẹp đẽ hahaha rằng không ai trên thế giới này có thể giải thích được suy nghĩ khôn ngoan nhất
Người học

Tức là bạn đang có ý định trong mã của mình rằng giá trị này không nên thay đổi bất cứ lúc nào.
Andez

51

Nói một cách rất thực tế:

Nếu bạn sử dụng const trong dll A và dll B tham chiếu const, giá trị của const đó sẽ được biên dịch thành dll B. Nếu bạn triển khai lại dll A với giá trị mới cho const đó, dll B sẽ vẫn sử dụng giá trị ban đầu.

Nếu bạn sử dụng chỉ đọc trong các tham chiếu dll A và dll B chỉ đọc, thì chỉ đọc đó sẽ luôn được tra cứu trong thời gian chạy. Điều này có nghĩa là nếu bạn triển khai lại dll A với giá trị mới cho chỉ đọc đó, dll B sẽ sử dụng giá trị mới đó.


4
Đây là một ví dụ thực tế tốt để hiểu sự khác biệt. Cảm ơn.
Shyju

Mặt khác, constcó thể có hiệu suất đạt được hơn readonly. Dưới đây là một lời giải thích sâu hơn với mã: dotnetperls.com/readonly
Dio Phung

2
Tôi nghĩ rằng thuật ngữ thực tế lớn nhất bị thiếu trong câu trả lời này: khả năng lưu trữ các giá trị được tính toán khi chạy vào readonlycác trường. Bạn không thể lưu trữ new object();trong một constvà điều đó có ý nghĩa bởi vì bạn không thể nướng những thứ không có giá trị như tham chiếu vào các hội đồng khác trong thời gian biên dịch mà không thay đổi danh tính.
biley

14

Có một trường hợp tiềm năng trong đó trình biên dịch có thể thực hiện tối ưu hóa hiệu suất dựa trên sự hiện diện của từ khóa chỉ đọc.

Điều này chỉ áp dụng nếu trường chỉ đọc cũng được đánh dấu là tĩnh . Trong trường hợp đó, trình biên dịch JIT có thể cho rằng trường tĩnh này sẽ không bao giờ thay đổi. Trình biên dịch JIT có thể tính đến điều này khi biên dịch các phương thức của lớp.

Ví dụ điển hình: lớp của bạn có thể có trường IsDebugLoggingEnables chỉ đọc tĩnh được khởi tạo trong hàm tạo (ví dụ: dựa trên tệp cấu hình). Khi các phương thức thực tế được biên dịch JIT, trình biên dịch có thể bao gồm toàn bộ các phần của mã khi ghi nhật ký gỡ lỗi không được bật.

Tôi chưa kiểm tra xem việc tối ưu hóa này có thực sự được thực hiện trong phiên bản hiện tại của trình biên dịch JIT hay không, vì vậy đây chỉ là suy đoán.


Có một nguồn cho điều này?
Sedat Kapanoglu

4
Trình biên dịch JIT hiện tại trên thực tế thực hiện điều này và kể từ CLR 3.5. github.com/dotnet/coreclr/issues/1079
mirhagk

Không tối ưu hóa có thể được thực hiện trên các trường chỉ đọc vì lý do đơn giản là các trường chỉ đọc không chỉ đọc mà chỉ đọc. Chúng chỉ là một gợi ý trình biên dịch mà hầu hết các trình biên dịch tôn trọng và các giá trị của các trường chỉ đọc có thể dễ dàng được ghi đè thông qua sự phản chiếu (mặc dù không phải trong mã đáng tin cậy một phần).
Christoph

9

Hãy nhớ rằng chỉ đọc áp dụng cho chính giá trị, vì vậy nếu bạn đang sử dụng loại tham chiếu chỉ đọc để bảo vệ tham chiếu khỏi bị thay đổi. Trạng thái của cá thể không được bảo vệ bởi chỉ đọc.


4

Đừng quên có một cách giải quyết để có được các readonlytrường được đặt bên ngoài bất kỳ hàm tạo nào bằng tham số out.

Một chút lộn xộn nhưng:

private readonly int _someNumber;
private readonly string _someText;

public MyClass(int someNumber) : this(data, null)
{ }

public MyClass(int someNumber, string someText)
{
    Initialise(out _someNumber, someNumber, out _someText, someText);
}

private void Initialise(out int _someNumber, int someNumber, out string _someText, string someText)
{
    //some logic
}

Thảo luận thêm tại đây: http://www.adamjamesnaylor.com/2013/01/23/Setting-Readonly-Fields-From-Chained-Constructor.aspx


4
Các trường vẫn được chỉ định trong hàm tạo .. không có "xung quanh" đó. Sẽ không có vấn đề gì nếu các giá trị là từ các biểu thức đơn, từ một loại phức tạp bị phân tách hoặc được gán qua cuộc gọi theo ngữ nghĩa tham chiếu của out..
user2864740

Điều này thậm chí không cố gắng trả lời câu hỏi.
Sheridan

2

Đáng ngạc nhiên, chỉ đọc thực sự có thể dẫn đến mã chậm hơn, như Jon Skeet tìm thấy khi thử nghiệm thư viện Noda Time của mình. Trong trường hợp này, một bài kiểm tra chạy trong 20 giây chỉ mất 4 giây sau khi xóa chỉ đọc.

https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-ineffic-of-readonly-fields/


3
Lưu ý rằng nếu trường thuộc loại readonly structC # 7.2, lợi ích của việc làm cho trường không đọc chỉ mất đi.
Jon Skeet

1

Nếu bạn có một giá trị được xác định trước hoặc được tính toán trước cần duy trì giống nhau trong suốt chương trình thì bạn nên sử dụng hằng số nhưng nếu bạn có một giá trị cần được cung cấp trong thời gian chạy nhưng một khi được gán sẽ giữ nguyên trong suốt chương trình bạn nên sử dụng chỉ đọc. ví dụ: nếu bạn phải gán thời gian bắt đầu chương trình hoặc bạn phải lưu trữ giá trị do người dùng cung cấp khi khởi tạo đối tượng và bạn phải hạn chế nó khỏi những thay đổi tiếp theo, bạn nên sử dụng chỉ đọc.


1

Thêm một khía cạnh cơ bản để trả lời câu hỏi này:

Các thuộc tính có thể được thể hiện dưới dạng chỉ đọc bằng cách bỏ settoán tử. Vì vậy, trong hầu hết các trường hợp, bạn sẽ không cần thêm readonlytừ khóa vào thuộc tính:

public int Foo { get; }  // a readonly property

Ngược lại với điều đó: Các trường cần readonlytừ khóa để đạt được hiệu quả tương tự:

public readonly int Foo; // a readonly field

Vì vậy, một lợi ích của việc đánh dấu một trường là readonlycó thể đạt được mức bảo vệ ghi tương tự như một thuộc tính mà không cần settoán tử - mà không phải thay đổi trường thành thuộc tính, nếu vì bất kỳ lý do gì, điều đó là mong muốn.


Có sự khác biệt trong hành vi giữa 2 không?
petrosmm

0

Hãy cẩn thận với các mảng chỉ đọc riêng tư. Nếu chúng được hiển thị một máy khách dưới dạng một đối tượng (bạn có thể thực hiện điều này cho COM xen kẽ như tôi đã làm) thì máy khách có thể thao tác các giá trị mảng. Sử dụng phương thức Clone () khi trả về một mảng dưới dạng đối tượng.


20
Không; phơi bày một ReadOnlyCollection<T>thay vì một mảng.
SLaks

Đây không phải là một bình luận không phải là một câu trả lời vì nó không cung cấp câu trả lời cho câu hỏi ...
Peter

Thật thú vị, tôi đã được yêu cầu đặt loại điều này như một câu trả lời, thay vì một bình luận khi tôi làm nó trên một bài đăng khác vào tuần trước.
Kyle Baran

Kể từ năm 2013, bạn có thể sử dụng ImmutableArray<T>, giúp tránh quyền anh đối với giao diện ( IReadOnlyList<T>) hoặc gói trong một lớp ( ReadOnlyCollection). Nó có hiệu suất tương đương với mảng bản địa: blog.msdn.microsoft.com/dotnet/2013/06/24/ mẹo
Charles Taylor

0

Có thể có một lợi ích hiệu suất trong WPF, vì nó loại bỏ nhu cầu về DependencyProperies đắt tiền. Điều này có thể đặc biệt hữu ích với các bộ sưu tập


0

Một phần thú vị khác của việc sử dụng đánh dấu chỉ đọc có thể là bảo vệ trường khỏi khởi tạo trong singleton.

ví dụ trong mã từ csharpindepth :

public sealed class Singleton
{
    private static readonly Lazy<Singleton> lazy =
        new Lazy<Singleton>(() => new Singleton());

    public static Singleton Instance { get { return lazy.Value; } }

    private Singleton()
    {
    }
}

chỉ đọc đóng vai trò nhỏ trong việc bảo vệ trường Singleton khỏi bị khởi tạo hai lần. Một chi tiết khác là đối với kịch bản được đề cập, bạn không thể sử dụng const vì const bắt buộc tạo trong thời gian biên dịch, nhưng singleton tạo ra khi chạy.


0

readonlycó thể được khởi tạo khi khai báo hoặc chỉ lấy giá trị của nó từ hàm tạo. Không giống như constnó phải được khởi tạo và khai báo cùng một lúc. readonly có tất cả mọi thứ const có, cộng với khởi tạo constructor

https://repl.it/HvRU/1

using System;

class MainClass {
    public static void Main (string[] args) {

        Console.WriteLine(new Test().c);
        Console.WriteLine(new Test("Constructor").c);
        Console.WriteLine(new Test().ChangeC()); //Error A readonly field 
        // `MainClass.Test.c' cannot be assigned to (except in a constructor or a 
        // variable initializer)
    }


    public class Test {
        public readonly string c = "Hello World";
        public Test() {

        }

        public Test(string val) {
          c = val;
        }

        public string ChangeC() {
            c = "Method";
            return c ;
        }
    }
}
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.