Introduction
Today we will learn the subtle difference between ObservableCollection and List. In nutshell both List and ObservableCollection inherits ICollection, IList interfaces. both are a collection of objects.
So when we already have a List why do we need ObservableCollection?
If you look at the implementation of ObservableCollection, You would see that it inherits INotifyCollectionChanged, INotifyPropertyChanged as shown in figure 1.
Figure 1: Class ObservableCollection
INotifyCollectionChanged
In figure 1 you can see there is NotifyCollectionChangedEventHandler, this is CollectionChanged event which notifies the listener whenever an item has been added or removed from the list.
INotifyPropertyChanged
In the above figure, there is event PropertyChangedEventHandler, which is PropertyChanged, this notifies the listener when value of the property has been changed.
Well this is what List is missing, which makes ObservableCollection so special.
This is good in theory, but we need to understand it practically. Let's create a small WPF application.
The APP
This is a WPF application, following the MVVM pattern. The object we are going to work with is Actor. Our screen which is MainWindow will have a ListBox to show the details of actors. A small form to create a new actor object with 2 buttons, one to create and another to alter the actor's details. Alright, having said that go ahead and create a WPF application. And inside a MainWindow adds the following code. This listing contains everything that is mentioned above.
<Window x:Class="ObservableCollectionVsList.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ObservableCollectionVsList"
mc:Ignorable="d"
Title="MainWindow" Height="320" Width="700">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock FontSize="15"
Foreground="DarkBlue"
HorizontalAlignment="Center"
Margin="5"
Text="Actors details:"/>
<ListView x:Name="ListViewAllActors"
Background="#EEEEEE"
HorizontalAlignment="Center"
ItemsSource="{Binding ListOfActors}"
Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal"
Width="520">
<TextBlock x:Name="TextBlockActorDetail"
FontWeight="Bold" >
<Run Text=" | "/>
<Run Text="Name: "
Foreground="#344CB7"/>
<Run Text="{Binding Name}"
Foreground="#CD1818"/>
<Run Text=" | "/>
<Run Text="Age: "
Foreground="#344CB7"/>
<Run Text="{Binding Age}"
Foreground="#CD1818"/>
<Run Text=" | "/>
<Run Text="DOB: "
Foreground="#344CB7"/>
<Run Text="{Binding DOB, StringFormat=D}"
Foreground="#CD1818"/>
<Run Text=" | Oscar: "
Foreground="#344CB7"/>
</TextBlock>
<CheckBox IsChecked="{Binding WonOscar}"/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Border Background="#11324D"
BorderBrush="White"
BorderThickness="1,1,1,1"
CornerRadius="30,30,30,30"
HorizontalAlignment="Center"
Margin="0 5 0 0"
Grid.Row="3">
<Grid x:Name="GridPost"
Margin="10 10 10 0"
Width="260">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label x:Name="LabelActorName"
Content="Actor Name:"
Foreground="White"/>
<Label x:Name="LabelDOB"
Content="DOB:"
Foreground="White"
Grid.Row="1"/>
<Label x:Name="LabelAge"
Content="Age:"
Foreground="White"
Grid.Row="2"/>
<Label x:Name="LabelOscar"
Content="Oscar:"
Foreground="White"
Grid.Row="3"/>
<TextBox x:Name="TextBoxActorName"
Text="{Binding ActorName}"
Height="20"
Width="150"
Grid.Column="1"/>
<TextBox x:Name="TextBoxDOB"
Text="{Binding ActorDOB, StringFormat=d}"
Height="20"
Width="150"
Grid.Column="1"
Grid.Row="1"/>
<TextBox x:Name="TextBoxActorAge"
Text="{Binding ActorAge}"
Height="20"
Width="150"
Grid.Column="1"
Grid.Row="2"/>
<CheckBox x:Name="CheckBoxOscar"
IsChecked="{Binding ActorWonOscar}"
Margin="0 4 0 0"
Grid.Column="1"
Grid.Row="3"/>
<StackPanel
Margin="20 10 0 20"
Orientation="Horizontal"
Grid.ColumnSpan="2"
Grid.Row="4">
<Button x:Name="ButtonAddActor"
Command="{Binding AddActorCommand}"
Background="#2E4C6D"
Content="Add"
Foreground="White"
Height="20"
HorizontalAlignment="Center"
Width="100"/>
<Button x:Name="ButtonUpdateActor"
Command="{Binding UpdateActorCommand}"
Background="#2E4C6D"
Content="Update"
Foreground="White"
Height="20"
HorizontalAlignment="Center"
Margin="10 0 0 0"
Width="100"/>
</StackPanel>
</Grid>
</Border>
</Grid>
</Window>
Listing 1: XAML for the Screen in figure 2
Run the project, you'll see the following screen. Forget about the data that we are going to add from ViewModel next.
Figure 2: WPF app to Add and Update collection
We need Actor's class, following snippet is for class Actor
internal class Actor
{
public int ID { get; set; }
public string? Name { get; set; }
public DateTime DOB { get; set; }
public int Age { get; set; }
public bool WonOscar { get; set; }
}
Listing 2: class Actor
Approach 1: List
In ViewModel, we need to create List of Actors.
private List<Actor> _listOfActors;
public List<Actor> ListOfActors
{
get { return _listOfActors; }
set { SetProperty(ref _listOfActors, value); }
}
Listing 3: List of Actors
For the Add button, we need the following AddActor(), This method takes the binding variables data and creates a new Actor object which is added to the list of Actors in listing 3.
private void AddActor()
{
ListOfActors.Add(new Actor()
{
ID = 4,
Name = ActorName,
DOB = ActorDOB,
Age = ActorAge,
WonOscar = ActorWonOscar
});
}
Listing 4: Add button's logic
On clicking the Update button, We are just updating the name of the actor, Just to prove the point that ObservableCollection's propertyChanged event gets triggered.
private void UpdateActor()
{
ListOfActors.FirstOrDefault(act => act.Name == ActorName).Age = ActorAge;
}
Listing 5: Update button's logic
Listing 6 is the overall ViewModel, With dummy data, commands and other binding properties.
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows.Input;
namespace ObservableCollectionVsList
{
internal class MainWindowViewModel : BindableBase
{
#region Properties
private List<Actor> _listOfActors;
public List<Actor> ListOfActors
{
get { return _listOfActors; }
set { SetProperty(ref _listOfActors, value); }
}
public string? ActorName { get; set; }
public DateTime ActorDOB { get; set; }
public int ActorAge { get; set; }
public bool ActorWonOscar { get; set; }
#endregion
#region ICommands
public ICommand AddActorCommand { get; set; }
public ICommand UpdateActorCommand { get; set; }
#endregion
#region Constructor
public MainWindowViewModel()
{
LoadCollectionData();
AddActorCommand = new DelegateCommand(AddActor);
UpdateActorCommand = new DelegateCommand(UpdateActor);
}
#endregion
private void LoadCollectionData()
{
ListOfActors = new List<Actor>
{
new Actor()
{
ID = 1,
Name = "Johnny Depp",
DOB = new DateTime(1963, 6, 9),
Age = 58,
WonOscar = false
},
new Actor()
{
ID = 2,
Name = "Leonardo DiCaprio",
DOB = new DateTime(1974, 11, 11),
Age = 46,
WonOscar = true
},
new Actor()
{
ID = 3,
Name = "Robert Downey Jr.",
DOB = new DateTime(1965, 4, 4),
Age = 56,
WonOscar = false
}
};
}
private void AddActor()
{
ListOfActors.Add(new Actor()
{
ID = 4,
Name = ActorName,
DOB = ActorDOB,
Age = ActorAge,
WonOscar = ActorWonOscar
});
}
private void UpdateActor()
{
ListOfActors.FirstOrDefault(act => act.Name == ActorName).Age = ActorAge;
}
}
}
Listing 6: ViewModel for MainWindow
Now let's begin with the magic. When you run the project for the first time, you see the output as figure 3. At the top, you'd see the dummy details that are coming from LoadCollectionData(), and in the form below we are entering details of Meryl Streep.
Figure 3: Adding new actor details
Once you click on an Add Button, In figure 4, you would see List is getting updated behind the scene but unable to update the UI. Well that's a flaw, INotifyCollectionChanged is not working, Let's see if INotifyPropertyChanged is working or not.
Figure 4: New details added to the list of object
In Figure 5, we are changing Johnny depp's age and clicking the update button, as you can notice no change has been reflected in the list.
Figure 5: Updating Johnny Depp's age but ListView is not reflecting the changes
But the list has been updated behind the scenes, as you can see in figure 6. So technically INotifyPropertyChanged is not working either. This is because of the reasons mentioned above.
Figure 6: Johnny Depp's age has been updated in the object
Approach 2: ObservableCollection
Let's make necessary changes, Update the code in viewmodel, change list to ObservableCollection.
private ObservableCollection<Actor> _listOfActors;
public ObservableCollection<Actor> ListOfActors
{
get { return _listOfActors; }
set { SetProperty(ref _listOfActors, value); }
}
Listing 7: ObservableCollection of Actors
Note: there is one more reference in the LoadCollectionData() method, change that to ObservableCollection as well.
Now let's run the project and add a few details. In figure 7, you can see as soon as new actor's details have been added to the list, The ListBox is automatically updated. This is because ObservableCollection uses INotifyCollectionChanged event to trigger the source that list has been modified.
Figure 7: Adding new record with ObservableCollection and ListView has added a new record
Now let's update the age of Meryl Streep from 72 to 70. You need to update the Actor class to reflect propertychanged events. Code snippet 8 shows how to achieve this. We are using the BindableBase class which implements INotifyPropertyChanged interface. We are only updating the Age property for now, because in our UpdateActor() we are only updating the age of an actor.
internal class Actor : BindableBase
{
public int ID { get; set; }
public string? Name { get; set; }
public DateTime DOB { get; set; }
private int _age;
public int Age
{
get { return _age; }
set { SetProperty(ref _age, value); }
}
public bool WonOscar { get; set; }
}
Listing 8: Actor class with BindableBase: INotifyPropertyChanged
Changing Age, In figure 8 you would see it has successfully updated the age of Meryl Streep and that has also been notified to UI, this is because of the PropertyChanged event.
Figure 7: Updating age of Meryl Streep with ObservableCollection and ListView is reflecting these changes
Summary
Today we learned the difference between list and ObservableCollection through an example. We saw how to create a WPF application to understand the INotifyCollectionChanged and INotifyPropertyChanged
Find attached source code for reference. Hope this article helps you to understand both list and ObservableCollection.
Happy Coding.