Inside the class there is a private variable of the list, which can vary according to the logic of the class. There is a public property that provides read-only elements for the internal list. So I need an event about changing the internal list item. What I need is nothing to do with the graphical interface. This is required for equipment management class. Here are two options, as I thought of doing, but how correctly I do not know. Maybe in general there are some other ways.

class TestClass1 { // Меняется внутри класса private bool[] _states; // Для чтения элементов массива _states извне. private ReadOnlyCollection<bool> _readOnlyStates; public ReadOnlyCollection<bool> States { get { return _readOnlyStates; } } // Событие, передающее индекс изменного элемента. public event Action<int> States1Changed; private void UpdateStates(int index, bool newValue) { if (_states[index] != newValue) _states[index] = newValue; } public TestClass1() { _states = new bool[20]; _readOnlyStates = Array.AsReadOnly(_states); } } class TestClass2 { // Меняется внутри класса private ObservableCollection<bool> _states; // Для чтения элементов массива _states извне. private ReadOnlyObservableCollection<bool> _readOnlyStates; public ReadOnlyObservableCollection<bool> States { get { return _readOnlyStates; } } // Для получения события об изменении сделать следующее // (States as INotifyCollectionChanged).CollectionChanged private void UpdateStates(int index, bool newValue) { if (_states[index] != newValue) _states[index] = newValue; } public TestClass2() { _states = new ObservableCollection<bool>(); _readOnlyStates = new ReadOnlyObservableCollection<bool>(_states); } } 

UPDATE 1

There was a need for such a list, I also don’t know how to act better. The list stores objects with properties. The class must be able to change the properties of the elements and notify the subscriber. The user can only read the properties of the elements. Only such an option occurred to me.

 class ListItem { public bool Property1 { get; private set; } public double Property2 { get; private set; } public ListItem(bool property1, double property2) { Property1 = property1; Property2 = property2; } } class TestClass { private List<ListItem> _list; private ReadOnlyCollection<ListItem> _readOnlyList; public TestClass() { _list = new List<ListItem>(); _readOnlyList = _list.AsReadOnly(); } public ReadOnlyCollection<ListItem> List { get { return _readOnlyList; } } public event ListChangedEventHandler ListChanged; public void UpdateProperty1(int index, bool property1) { if (_list[index].Property1 != property1) { _list[index] = new ListItem(property1, _list[index].Property2); ListChanged(this, new ListChangedEventArgs(ListChangedType.ItemChanged, index)); } } public void UpdateProperty2(int index, double property2) { if (_list[index].Property2 != property2) { _list[index] = new ListItem(_list[index].Property1, property2); ListChanged(this, new ListChangedEventArgs(ListChangedType.ItemChanged, index)); } } } 

UPDATE 2

I came up with this implementation. The user is only open IMortarInfo for reading. Inside there is a closed implementation of this interface MortarInfo with the ability to edit. Also added INotifyPropertyChanged to simplify writing code with events. Because of the impossibility of bringing IList<MortarInfo> to IList<IMortarInfo> I had to make a crutch.

 public interface IMortarInfo : INotifyPropertyChanged { double Angle { get; } bool State { get; } } public class Mortars { private class MortarInfo : NotifyPropertyChanged, IMortarInfo { private double _angle; private bool _state; public double Angle { get { return _angle; } set { if (_angle == value) return; _angle = value; OnPropertyChanged(); } } public bool State { get { return _state; } set { if (_state == value) return; _state = value; OnPropertyChanged(); } } public MortarInfo() : this(0, false) { } public MortarInfo(double angle, bool state) { _angle = angle; _state = state; } } private readonly BindingList<MortarInfo> _mortarsInfo; private readonly List<IMortarInfo> _listForReadOnlyMortarsInfo; private readonly ReadOnlyCollection<IMortarInfo> _readOnlyMortarsInfo; public ReadOnlyCollection<IMortarInfo> MortarsInfo { get { return _readOnlyMortarsInfo; } } public event ListChangedEventHandler MortarInfoChanged; public Mortars() { const int mortarsCount = 6; _mortarsInfo = new BindingList<MortarInfo>(); _mortarsInfo.ListChanged += ListChanged; _listForReadOnlyMortarsInfo = new List<IMortarInfo>(); _readOnlyMortarsInfo = _listForReadOnlyMortarsInfo.AsReadOnly(); for (int i = 0; i < mortarsCount; i++) _mortarsInfo.Add(new MortarInfo()); } private void ListChanged(object sender, ListChangedEventArgs e) { switch(e.ListChangedType) { case ListChangedType.ItemAdded: _listForReadOnlyMortarsInfo.Add(_mortarsInfo[e.NewIndex]); break; case ListChangedType.ItemDeleted: _listForReadOnlyMortarsInfo.RemoveAt(e.NewIndex); break; } if (MortarInfoChanged != null) MortarInfoChanged(this, e); } } 

You can also do without a crutch, but then during editing you will have to do ghost types.

 public class Mortars { private readonly BindingList<IMortarInfo> _mortarsInfo; private readonly ReadOnlyCollection<IMortarInfo> _readOnlyMortarsInfo; public ReadOnlyCollection<IMortarInfo> MortarsInfo { get { return _readOnlyMortarsInfo; } } public event ListChangedEventHandler MortarInfoChanged { add { _mortarsInfo.ListChanged += value; } remove { _mortarsInfo.ListChanged -= value; } } public Mortars() { const int mortarsCount = 6; _mortarsInfo = new BindingList<IMortarInfo>(); _readOnlyMortarsInfo = new ReadOnlyCollection<IMortarInfo>(_mortarsInfo); for (int i = 0; i < mortarsCount; i++) _mortarsInfo.Add(new MortarInfo()); } public void SomeMethod() { (_mortarsInfo[2] as MortarInfo).State = true; } } 

    2 answers 2

    Code based on a new question description. Comments are taken from the text of the question.

     using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; class Test { List<int> list = null; // закрытая переменная списка public Test() { list = new List<int>(); } // открытое свойство, предоставляющее элементы внутреннего списка только для чтения. public ReadOnlyCollection<int> List { get { return list.AsReadOnly(); } } // событие об изменении элемента внутреннего списка. public event ListChangedEventHandler ListChanged = delegate { }; public void SetValue(int index, int v) { list[index] = v; this.ListChanged(this, new ListChangedEventArgs(ListChangedType.ItemChanged, index)); } } 

    List.AsReadOnly() - returns ReadOnlyCollection (is a wrapper for the List). The source code is here .

    • Well, in general, you are for my first version with minor amendments. Only I would do a better ReadOnlyCollection in the form of a closed field, which we always return. So it is not required at each request to generate an object, which will then be removed by the collector. But I like your event more. - Nodon
    • I made an addition to the question, I am interested in your opinion - Nodon
    • added to the answer about .AsReadOnly (). and also: if there are a lot of Test objects, but little memory, then it’s better not to create a separate field for the reference to ReadOnlyCollection - since field takes up memory - Stack
    • "I made an addition to the question" - I see. The first thing you need to do is redo: Now in the code there are two calls to _list[index].Property* is twice the same element is sampled. if the array is very large and performance is critical, then it is better to remove one selection as follows: var v = _list[index]; v.Property* var v = _list[index]; v.Property* - Stack
    • There is not a large array. I am more interested in what would not be every time the call event. - Nodon

    If you want to track property changes on collection items, you must use a BindingList<T> instead of an ObservableCollection<T> .
    An example is here .

    UPDATE:

    An example for a collection that consists of numbers.
    After binding the collection to the DataGrid, the change of values ​​in the collection is displayed in the DataGrid.

     <Window x:Class="WpfApplication1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <DataGrid ItemsSource="{Binding}" AutoGenerateColumns="False" CanUserAddRows="False" > <DataGrid.Columns> <DataGridTextColumn Header="Values" Binding="{Binding}" /> </DataGrid.Columns> </DataGrid> 

     using System; using System.ComponentModel; using System.Windows; using System.Windows.Threading; namespace WpfApplication1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var c = new BindingList<long>(); // или ObservableCollection<long>(); this.DataContext = c; c.Add(DateTime.Now.Ticks); var t = new DispatcherTimer() { Interval = TimeSpan.FromSeconds(0.5) }; var r = new Random(); t.Tick += (s, e) => { if (c.Count >= 5) c[r.Next(0, c.Count-1)] = DateTime.Now.Ticks; else c.Add(DateTime.Now.Ticks); }; t.Start(); } } } 
    • I just have an array of elements of type bool for example, and not the types that implement INotifyPropertyChanged . - Nodon
    • And here I’ve looked at the BindingList as it’s not very optimized and it’s better to make an ObservableCollection descendant that will handle the PropertyChanged events on the elements. Here is the implementation of stackoverflow.com/questions/1427471/… stackoverflow.com/questions/8490533/… - Nodon
    • @Nodon "I just have an array of bool elements" - added an example to the answer. - Stack
    • @Nodon "BindingList does not seem to be very optimized" - did not check, but you can see the source BindingList - here . - Stack
    • I passed the question there, I realized that for the first time I wrote some kind of difficult to understand stream of consciousness. - Nodon