Tại sao ReadOnlyObservableCollection.CollectionChanged không được công khai?


86

Tại sao được ReadOnlyObservableCollection.CollectionChangedbảo vệ mà không được công khai (như tương ứng ObservableCollection.CollectionChanged)?

Việc sử dụng triển khai bộ sưu tập là INotifyCollectionChangedgì nếu tôi không thể truy cập CollectionChangedsự kiện?


1
Ra khỏi tò mò, tại sao bạn sẽ mong đợi một bộ sưu tập chỉ đọc để thay đổi? Chắc chắn sau đó nó sẽ không được đọc chỉ?
workmad3

83
Câu hỏi ngược lại: Tại sao tôi không mong đợi một ObservableCollection thay đổi? Quan sát nó có ích gì nếu không có gì thay đổi? Chà, bộ sưu tập chắc chắn sẽ thay đổi, nhưng tôi có những người tiêu dùng chỉ được phép quan sát nó. Nhìn mà không chạm ...
Oskar

1
Gần đây tôi cũng gặp phải vấn đề này. Về cơ bản ObservableCollection không triển khai sự kiện thay đổi INotifyCollection đúng cách. Tại sao C # cho phép một lớp hạn chế các sự kiện giao diện truy cập nhưng không cho phép các phương thức giao diện?
djskinner

35
Tôi phải bỏ phiếu cho điều này là hoàn toàn điên rồ. Tại sao trên thế giới, ReadOnlyObservableCollection vẫn tồn tại nếu bạn không thể đăng ký các sự kiện CollectionChanged? WTF là điểm? Và đối với tất cả những người luôn nói rằng một bộ sưu tập chỉ đọc sẽ không bao giờ thay đổi, hãy suy nghĩ thật kỹ về những gì bạn đang nói.
MojoFilter

9
"chỉ đọc" không có nghĩa là "bất biến" như một số người vẫn nghĩ. Nó chỉ có nghĩa là mã chỉ có thể xem bộ sưu tập thông qua một thuộc tính như vậy không được phép thay đổi nó. Nó thực sự có thể được thay đổi khi mã thêm hoặc xóa các thành viên thông qua bộ sưu tập cơ bản. Người đọc vẫn có thể được thông báo rằng các thay đổi đã xảy ra, ngay cả khi họ không thể tự thay đổi bộ sưu tập. Họ vẫn có thể cần phải tự mình đáp ứng các thay đổi. Tôi không thể nghĩ ra lý do hợp lệ nào để hạn chế thuộc tính CollectionChanged như đã được thực hiện trong trường hợp này.
Gil

Câu trả lời:



16

Tôi đã tìm thấy một cách cho bạn về cách làm điều này:

ObservableCollection<string> obsCollection = new ObservableCollection<string>();
INotifyCollectionChanged collection = new ReadOnlyObservableCollection<string>(obsCollection);
collection.CollectionChanged += new NotifyCollectionChangedEventHandler(collection_CollectionChanged);

Bạn chỉ cần tham khảo bộ sưu tập của mình một cách rõ ràng bằng giao diện INotifyCollectionChanged .


14
Xin lưu ý rằng ReadOnlyObservableCollection là một trình bao bọc xung quanh ObservableCollection - sẽ thay đổi. Người tiêu dùng ReadOnlyObservableCollection chỉ được phép quan sát những thay đổi này, không được tự mình thay đổi bất cứ điều gì.
Oskar

Bạn không nên dựa vào thực tế này, bộ sưu tập chỉ đọc sẽ không thay đổi trong mọi trường hợp, vì nó được gọi là "chỉ đọc". Đây là sự hiểu biết của tôi.
Restuta

4
+1 cho giải pháp đúc. Nhưng đối với việc quan sát một bộ sưu tập chỉ đọc là không hợp lý: Nếu bạn có quyền truy cập chỉ đọc vào cơ sở dữ liệu, bạn có mong đợi nó không bao giờ thay đổi không?
djskinner

16
Tôi không thấy khó khăn ở đây là gì. ReadOnlyObservableCollection là một lớp cung cấp cách CHỈ ĐỌC để quan sát một tập hợp. Nếu lớp học của tôi có một bộ sưu tập, chẳng hạn, các phiên và tôi không muốn mọi người có thể thêm hoặc xóa các phiên khỏi bộ sưu tập của mình, nhưng vẫn quan sát chúng, thì ReadOnlyObservableCollection là phương tiện hoàn hảo cho điều đó. Bộ sưu tập chỉ được đọc cho người dùng trong lớp của tôi, nhưng tôi có một bản sao đọc / ghi để sử dụng cho riêng mình.
scwagner

1
Đang truy cập mã Net của ReadOnlyObservableCollectionbạn sẽ tìm thấy điều này: event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged. Triển khai giao diện rõ ràng của sự kiện.
Mike de Klerk

7

Tôi biết bài đăng này đã cũ, tuy nhiên, mọi người nên dành thời gian để hiểu các mẫu được sử dụng trong .NET trước khi nhận xét. Bộ sưu tập chỉ đọc là một trình bao bọc trên một bộ sưu tập hiện có ngăn người tiêu dùng sửa đổi trực tiếp, hãy nhìn vào ReadOnlyCollectionvà bạn sẽ thấy rằng đó là một trình bao bọc trên một bộ sưu tập IList<T>có thể thay đổi được hoặc có thể không. Các bộ sưu tập bất biến là một vấn đề khác và được bao phủ bởi thư viện bộ sưu tập bất biến mới

Nói cách khác, chỉ đọc không giống với bất biến !!!!

Điều đó sang một bên, ReadOnlyObservableCollectionnên thực hiện ngầm INotifyCollectionChanged.


5

Chắc chắn có lý do chính đáng để bạn muốn đăng ký nhận các thông báo đã thay đổi trong bộ sưu tập trên ReadOnlyObservableCollection . Vì vậy, để thay thế cho việc chỉ truyền bộ sưu tập của bạn dưới dạng INotifyCollectionChanged , nếu bạn tình cờ phân lớp con ReadOnlyObservableCollection , thì phần sau cung cấp một cách thuận tiện hơn về mặt cú pháp để truy cập sự kiện CollectionChanged :

    public class ReadOnlyObservableCollectionWithCollectionChangeNotifications<T> : ReadOnlyObservableCollection<T>
{
    public ReadOnlyObservableCollectionWithCollectionChangeNotifications(ObservableCollection<T> list)
        : base(list)
    {
    }

    event System.Collections.Specialized.NotifyCollectionChangedEventHandler CollectionChanged2
    {
        add { CollectionChanged += value; }
        remove { CollectionChanged -= value; }
    }
}

Điều này đã làm việc tốt cho tôi trước đây.


5

Bạn có thể bỏ phiếu cho mục nhập lỗi trên Microsoft Connect mô tả sự cố này: https://connect.microsoft.com/VisualStudio/feedback/details/641395/readonlyobservablecollection-t-collectionchanged-event-should-be-public

Cập nhật:

Cổng Connect đã bị Microsoft tắt. Vì vậy liên kết trên không hoạt động nữa.

Thư viện Khung ứng dụng Win (WAF) của tôi cung cấp giải pháp: Lớp ReadOnlyObservableList :

public class ReadOnlyObservableList<T> 
        : ReadOnlyObservableCollection<T>, IReadOnlyObservableList<T>
{
    public ReadOnlyObservableList(ObservableCollection<T> list)
        : base(list)
    {
    }

    public new event NotifyCollectionChangedEventHandler CollectionChanged
    {
        add { base.CollectionChanged += value; }
        remove { base.CollectionChanged -= value; }
    }

    public new event PropertyChangedEventHandler PropertyChanged
    {
        add { base.PropertyChanged += value; }
        remove { base.PropertyChanged -= value; }
    }
}

Kết nối đã ngừng hoạt động. Tôi sẽ hoàn toàn bỏ phiếu
findusl

1

Như đã trả lời, bạn có hai tùy chọn: bạn có thể truyền ReadOnlyObservableCollection<T>tới giao diện INotifyCollectionChangedđể truy cập CollectionChangedsự kiện được triển khai rõ ràng hoặc bạn có thể tạo lớp trình bao bọc của riêng mình để thực hiện điều đó một lần trong hàm tạo và chỉ nối các sự kiện của trình bao bọc ReadOnlyObservableCollection<T>.

Một số thông tin chi tiết bổ sung về lý do tại sao vấn đề này vẫn chưa được khắc phục:

Như bạn có thể thấy từ mã nguồn , ReadOnlyObservableCollection<T>là một lớp công khai, không niêm phong (tức là có thể kế thừa), nơi các sự kiện được đánh dấu protected virtual.

Có nghĩa là, có thể có các chương trình được biên dịch với các lớp được dẫn xuất từ ​​đó ReadOnlyObservableCollection<T>, với các định nghĩa sự kiện bị ghi đè nhưng protectedkhả năng hiển thị. Các chương trình đó sẽ chứa mã không hợp lệ khi publickhả năng hiển thị của sự kiện được thay đổi thành trong lớp cơ sở, vì nó không được phép hạn chế khả năng hiển thị của một sự kiện trong các lớp dẫn xuất.

Vì vậy, thật không may, việc tạo ra protected virtualcác sự kiện publicsau này là một thay đổi phá vỡ nhị phân, và do đó nó sẽ không được thực hiện nếu không có lý do chính xác, điều mà tôi e rằng "Tôi phải ép kiểu đối tượng một lần để đính kèm các trình xử lý".

Nguồn: GitHub bình luận của Nick Guererra, ngày 19 tháng 8 năm 2015


0

Điều này đã được truy cập hàng đầu trên google vì vậy tôi nghĩ rằng tôi sẽ thêm giải pháp của mình trong trường hợp người khác tìm kiếm điều này.

Sử dụng thông tin ở trên (về việc cần truyền sang INotifyCollectionChanged ), tôi đã thực hiện hai phương thức mở rộng là đăng ký và hủy đăng ký.

Giải pháp của tôi - Phương pháp mở rộng

public static void RegisterCollectionChanged(this INotifyCollectionChanged collection, NotifyCollectionChangedEventHandler handler)
{
    collection.CollectionChanged += handler;
}

public static void UnregisterCollectionChanged(this INotifyCollectionChanged collection, NotifyCollectionChangedEventHandler handler)
{
    collection.CollectionChanged -= handler;
}

Thí dụ

IThing.cs

public interface IThing
{
    string Name { get; }
    ReadOnlyObservableCollection<int> Values { get; }
}

Sử dụng các phương pháp mở rộng

public void AddThing(IThing thing)
{
    //...
    thing.Values.RegisterCollectionChanged(this.HandleThingCollectionChanged);
}

public void RemoveThing(IThing thing)
{
    //...
    thing.Values.UnregisterCollectionChanged(this.HandleThingCollectionChanged);
}

Giải pháp của OP

public void AddThing(IThing thing)
{
    //...
    INotifyCollectionChanged thingCollection = thing.Values;
    thingCollection.CollectionChanged += this.HandleThingCollectionChanged;
}

public void RemoveThing(IThing thing)
{
    //...
    INotifyCollectionChanged thingCollection = thing.Values;
    thingCollection.CollectionChanged -= this.HandleThingCollectionChanged;
}

Phương án 2

public void AddThing(IThing thing)
{
    //...
    (thing.Values as INotifyCollectionChanged).CollectionChanged += this.HandleThingCollectionChanged;
}

public void RemoveThing(IThing thing)
{
    //...
    (thing.Values as INotifyCollectionChanged).CollectionChanged -= this.HandleThingCollectionChanged;
}

0

Giải pháp

ReadOnlyObservableCollection.CollectionChanged không được hiển thị (vì những lý do hợp lệ được nêu trong các câu trả lời khác), vì vậy, hãy tạo lớp trình bao bọc của riêng chúng tôi để hiển thị nó:

/// <summary>A wrapped <see cref="ReadOnlyObservableCollection{T}"/> that exposes the internal <see cref="CollectionChanged"/>"/>.</summary>
public class ObservableReadOnlyCollection<T> : ReadOnlyObservableCollection<T>
{
    public new NotifyCollectionChangedEventHandler CollectionChanged;

    public ObservableReadOnlyCollection(ObservableCollection<T> list) : base(list) { /* nada */ }

    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args) => 
        CollectionChanged?.Invoke(this, args);
}

Giải trình

Mọi người đã hỏi tại sao bạn muốn quan sát các thay đổi đối với bộ sưu tập chỉ đọc, vì vậy tôi sẽ giải thích một trong nhiều tình huống hợp lệ; khi bộ sưu tập chỉ đọc bao bọc một bộ sưu tập nội bộ riêng tư có thể thay đổi.

Đây là một tình huống như vậy:

Giả sử bạn có một dịch vụ cho phép thêm và xóa các mục vào bộ sưu tập nội bộ từ bên ngoài dịch vụ. Bây giờ, giả sử bạn muốn tiết lộ các giá trị của bộ sưu tập nhưng bạn không muốn người tiêu dùng trực tiếp thao túng bộ sưu tập; vì vậy bạn bọc bộ sưu tập nội bộ trong a ReadOnlyObservableCollection.

Lưu ý rằng để bao bọc bộ sưu tập bên trong với ReadOnlyObservableCollectionbộ sưu tập bên trong thì bắt buộc phải lấy từ ObservableCollectionhàm tạo của ReadOnlyObservableCollection.

Bây giờ, giả sử bạn muốn thông báo cho người tiêu dùng về dịch vụ khi bộ sưu tập nội bộ thay đổi (và do đó khi bộ sưu tập tiếp xúc ReadOnlyObservableCollectionthay đổi). Thay vì cán triển khai thực hiện riêng của bạn, bạn chỉ muốn để lộ sự CollectionChangedcủa ReadOnlyObservableCollection. Thay vì buộc người tiêu dùng phải đưa ra giả định về việc triển khai ReadOnlyObservableCollection, bạn chỉ cần trao đổi ReadOnlyObservableCollectionvới tùy chỉnh này ObservableReadOnlyCollectionvà bạn đã hoàn tất.

Các ObservableReadOnlyCollectionda ReadOnlyObservableCollection.CollectionChangedvới nó là của riêng, và chỉ đơn giản là đi trên tất cả các bộ sưu tập thay đổi các sự kiện để bất cứ xử lý sự kiện kèm theo.

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.