2011-02-04 27 views
9

Nếu tôi phơi bày một IEnumerable<T> làm tài sản của một lớp, có khả năng nó có thể bị đột biến bởi người dùng lớp học, và nếu vậy cách tốt nhất để bảo vệ chống lại đột biến là gì, trong khi vẫn giữ kiểu thuộc tính tiếp xúc IEnumerable<T> ?Có an toàn để hiển thị một IEnumerable trong một tài sản không?

+0

Có một số cảnh báo khi sử dụng Bộ sưu tập làm tài sản, vui lòng kiểm tra liên kết hướng dẫn thiết kế này từ MSDN https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/guidelines-for-collections –

Trả lời

14

Tùy thuộc vào những gì bạn đang quay trở lại. Nếu bạn quay trở lại (nói) một biến thể List<string> thì khách hàng thực sự có thể truyền nó trở lại List<string> và biến đổi nó.

Cách bạn bảo vệ dữ liệu của mình tùy thuộc vào những gì bạn phải bắt đầu. ReadOnlyCollection<T> là lớp trình bao bọc tốt, giả sử bạn có một số IList<T> để bắt đầu.

Nếu khách hàng của bạn sẽ không được hưởng lợi từ giá trị trả về thực hiện IList<T> hoặc ICollection<T>, bạn luôn có thể làm điều gì đó như:

public IEnumerable<string> Names 
{ 
    get { return names.Select(x => x); } 
} 

mà hiệu quả kết thúc tốt đẹp bộ sưu tập trong một iterator. (Có nhiều cách khác nhau của việc sử dụng LINQ để ẩn nguồn ... mặc dù nó không được ghi lại mà toán tử ẩn nguồn và không. Ví dụ: Gọi Skip(0)làm ẩn nguồn trong bản triển khai của Microsoft, nhưng không phải là được ghi lại để làm như vậy.)

Select chắc chắn nên ẩn nguồn.

+1

Như thường lệ, Jon Skeet cung cấp một câu trả lời tốt. Tôi chỉ cần thêm rằng các loại thu thập dữ liệu khác nhau không được sử dụng để bảo mật trong các chương trình. Nếu chúng ta chạy trong môi trường tin tưởng đầy đủ, người ta luôn có thể đào sâu vào các vật thể của chúng ta để tìm thấy bất cứ thứ gì ẩn bên trong. Chúng ta cần phải sử dụng các cơ chế bảo mật ứng dụng nếu chúng ta thực sự cần bảo vệ các bộ sưu tập khỏi những thay đổi. –

1

Bộ sưu tập có thể được đưa trở về kiểu gốc và nếu có thể thay đổi được thì có thể bị tắt tiếng.

Một cách để tránh khả năng bị đột biến ban đầu đang trả lại bản sao của danh sách.

+0

Làm một bản sao là một cách có thể, đúng, nhưng đó thực sự là "cách tốt nhất"? Đôi khi có thể, nhưng đôi khi không. –

+0

Trả lại một bản sao tốn kém cho các bộ sưu tập lớn. Trừ khi tôi thực sự cần ngữ nghĩa của một bản sao tôi thích hàm bao như JonSkeet gợi ý. – CodesInChaos

+0

@Al, @CodeInChaos - Điểm công bằng ... trả lời sửa đổi. – Oded

2

Người dùng có thể quay lại lớp thu thập, vì vậy hãy phơi bày.

collection.Select(x => x) 

và điều này sẽ nhận được một IEnumerable tạo mà không thể được đúc vào bộ sưu tập

+0

Tôi thích điều này. Hiểu rằng nếu bộ sưu tập là một kiểu tham chiếu, các thành viên của các thành phần sẽ vẫn có thể thay đổi được, tuy nhiên việc thay đổi cardinality và thứ tự của số đếm bằng cách slurping nó vào một danh sách hoặc mảng sẽ không ảnh hưởng đến bộ sưu tập gốc. – KeithS

0

tôi sẽ không đề nghị gói một IEnumerable trong một iterator để ngăn chặn người nhận từ monkeying với các kết nối cơ bản mới. độ nghiêng của tôi sẽ được sử dụng một cái gì đó wrapper như:

public struct WrappedEnumerable<T> : IEnumerable<T> 
{ 
    IEnumerable<T> _dataSource; 
    public WrappedEnumerable(IEnumerable<T> dataSource) 
    { 
     _dataSource = dataSource; 
    } 
    public IEnumerator<T> GetEnumerator() 
    { 
     return _dataSource.GetEnumerator(); 
    } 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return ((System.Collections.IEnumerable)_dataSource).GetEnumerator(); 
    } 
} 

Nếu kiểu trả về của các thuộc tính là IEnumerable<T>, kiểu ép buộc WrappedEnumerable<T>-IEnumerable<T> sẽ hộp cấu trúc và thực hiện các hành vi và kết hợp hiệu suất những của một lớp . Tuy nhiên, nếu thuộc tính, được định nghĩa là kiểu trả về WrappedEnumerable<T>, thì có thể lưu bước đánh bóng trong trường hợp mã gọi điện hoặc gán trở lại thuộc tính loại WrappedEnumerable<T> (rất có thể là kết quả của một cái gì đó như var myKeys = myCollection.Keys;) hoặc đơn giản chỉ sử dụng thuộc tính trực tiếp trong vòng lặp "foreach". Lưu ý rằng nếu điều tra viên trả về bởi GetEnumerator() sẽ là một cấu trúc, điều đó sẽ vẫn phải được đóng hộp trong mọi trường hợp.

Lợi thế hiệu suất của việc sử dụng cấu trúc chứ không phải là lớp thường sẽ khá nhỏ; tuy nhiên, về mặt khái niệm, việc sử dụng một cấu trúc sẽ phù hợp với đề xuất chung rằng các thuộc tính không tạo ra các cá thể đối tượng heap mới. Xây dựng một thể hiện cấu trúc mới mà không chứa gì, nhưng một tham chiếu đến một đối tượng heap hiện có là rất rẻ.Nhược điểm lớn nhất khi sử dụng cấu trúc như được định nghĩa ở đây là nó sẽ khóa trong hành vi của thứ được trả về mã gọi, trong khi chỉ cần trả về IEnumerable<T> sẽ cho phép các cách tiếp cận khác.

Cũng lưu ý rằng nó có thể trong một số trường hợp có thể để loại bỏ các yêu cầu đối với bất kỳ boxing và khai thác tối ưu hóa vịt-gõ vào C# và vb.net foreach vòng lặp nếu ta dùng một loại như:

public struct FancyWrappedEnumerable<TItems,TEnumerator,TDataSource> : IEnumerable<TItems> where TEnumerator : IEnumerator<TItems> 
{ 
    TDataSource _dataSource; 
    Func<TDataSource,TEnumerator> _convertor; 
    public FancyWrappedEnumerable(TDataSource dataSource, Func<TDataSource, TEnumerator> convertor) 
    { 
     _dataSource = dataSource; 
     _convertor = convertor; 
    } 
    public TEnumerator GetEnumerator() 
    { 
     return _convertor(_dataSource); 
    } 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return _convertor(_dataSource); 
    } 
} 

Các đại biểu convertor có thể là một đại biểu tĩnh, và do đó không yêu cầu bất kỳ tạo đối tượng heap tại thời gian chạy (bên ngoài khởi tạo lớp). Sử dụng phương pháp này, nếu muốn trả lại một điều tra viên từ một số List<int>, loại trả về thuộc tính sẽ là FancyWrappedEnumerable<int, List<int>.Enumerator, List>. Có lẽ hợp lý nếu người gọi chỉ sử dụng thuộc tính trực tiếp trong một vòng lặp foreach hoặc trong tuyên bố var, nhưng khá icky nếu người gọi muốn khai báo vị trí lưu trữ của loại theo cách không thể sử dụng var.

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