Observable Collections

I didn't think it was possible, but .NET surprised me yet again with a cool feature I never knew existed: The ObservableCollection. This became available in .NET 3.0.

In essence, an ObservableCollection is a collection with an event you can connect to. The event fires when the collection changes. As usual, working with the .NET classes is so ridiculously easy, it feels like cheating.

However, using the ObservableCollection, or any other object with an event handler, can be tricky in Asp.Net.  Asp.Net member RichardD pointed out some errors in my original post and I would like to thank him for his comments.   He also showed me a better way to use HttpContext.Current.Handler which simplified the code.  However, since his comments were no longer relevant after the rewrite I deleted them…sorry buddy.

The following is small test program to illustrate how the ObservableCollection works. To start, create an ObservableCollection and then store it in the Session object so it will persist between page post backs. I also added the code to pull it out of Session state when there is a page post back:  

public partial class _Default : System.Web.UI.Page
{
    public ObservableCollection<int> MyInts;
 
    // ---- Page_Load ------------------------------
 
    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack == false)
        {
            MyInts = new ObservableCollection<int>();
            MyInts.CollectionChanged += CollectionChangedHandler;
 
            Session["MyInts"] = MyInts;  // store for use between postbacks
        }
        else
        {
            MyInts = Session["MyInts"] as ObservableCollection<int>;
        }
    }


Here's the event handler I hooked up to the ObservableCollection, it writes status strings to a ListBox. Note: the event handler is a static method.

    // ---- CollectionChangedHandler -----------------------------------
    //
    // Something changed in the Observable collection
    // Member is static, otherwise it is a reference to the page
    // object and the object will not be Garbage Collected.
 
    static public void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e)
    { 
        _Default CurrentPage = System.Web.HttpContext.Current.Handler as _Default;
 
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                CurrentPage.ListBoxHistory.Items.Add("Add: " + e.NewItems[0]);               
                break;
 
            case NotifyCollectionChangedAction.Remove: 
                CurrentPage.ListBoxHistory.Items.Add("Remove: " + e.OldItems[0]);
                break;
 
            case NotifyCollectionChangedAction.Reset:
                CurrentPage.ListBoxHistory.Items.Add("Reset: ");
                break;
 
            default:
                CurrentPage.ListBoxHistory.Items.Add(e.Action.ToString());
                break;             
        }
    } 

In the original post, I had the event handler as a normal member function of the page.  This caused two problems:

  • Since the ObservableCollection was put in Session state, and it had a reference to the page object, the initial page object was NOT released for garbage collection.  I verified this by putting trace statements in the page’s constructor and destructor and verified the initial page object persisted.  On subsequent post backs the page objects were created, released and discarded correctly.  The initial page object remained in memory...not too big a deal but it just ain't right.
  • A member function of a page should have access to the page’s controls.  However, since the function was in the original page object that was no longer active, it did not have access to any controls in the current page object that were to be rendered and sent to the browser. 

Next, add some buttons and code to exercise the ObservableCollection:

<button id="ButtonAdd"  önclick="ButtonAdd_Click" text="Add"  runat="server" />
<button id="ButtonRemove"  önclick="ButtonRemove_Click" text="Remove"  runat="server" />
<button id="ButtonReset"  önclick="ButtonReset_Click" text="Reset"  runat="server" />
<button id="ButtonList"  önclick="ButtonList_Click" text="List"  runat="server" />
<br />   
<textbox id="TextBoxInt"  runat="server" width="51px" />
<br />  
<listbox id="ListBoxHistory"  runat="server" width="195px" height="255px"> 

 

// ---- Add Button --------------------------------------
 
protected void ButtonAdd_Click(object sender, EventArgs e)
{
    int Temp;
    if (int.TryParse(TextBoxInt.Text, out Temp) == true)
        MyInts.Add(Temp);
}
 
// ---- Remove Button --------------------------------------
 
protected void ButtonRemove_Click(object sender, EventArgs e)
{
    int Temp;
    if (int.TryParse(TextBoxInt.Text, out Temp) == true)
        MyInts.Remove(Temp);
}
 
// ---- Button Reset -----------------------------------
 
protected void ButtonReset_Click(object sender, EventArgs e)
{
    MyInts.Clear();
}
 
// ---- Button List --------------------------------------
 
protected void ButtonList_Click(object sender, EventArgs e)
{
    ListBoxHistory.Items.Add("MyInts:");
    foreach (int i in MyInts)
    {
        // a bit of tweaking to get the text to be indented
        ListItem LI = new ListItem("  " + i.ToString());
        LI.Text = Server.HtmlDecode(LI.Text);
        ListBoxHistory.Items.Add(LI);
    }
}

Here's what it looks like after entering some numbers and clicking some buttons:

An interesting note: From the online help:

Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe

What does that mean to Asp.Net developers?

If you are going to share an ObservableCollection among different sessions, i.e. different threads, you’d better make it a static object and not use the Application state collection….right?   WRONG!

The documentation is easily misread (there are a LOT of posts on this subject on a lot of web sites).  It means static MEMBERS of the ObservableCollection class are thread-safe, not a static instance of the class itself.  If the collection is to be shared among threads, you have to synchronize the access.  Once again, I’d like to thank  RichardD for his constant nagging and badgering me to get it right.  Thanks pal!

I hope someone finds this useful.

Steve Wellens

 

8 Comments

  • I am quite amazed to know that .net framework have this :)

    Observerable collection is something I am looking for quite some time. thanks for sharing such wonderful article

  • "If you are going to share an ObservableCollection among different sessions, you'd better make it a static object."

    I still disagree with this comment. I think you're getting confused by the boilerplate text that Microsoft add to every class. When they refer to "public static members of this type", they mean "public static members *defined by* this type", not "public static fields *containing an instance of* this type". [1]

    Putting an observable collection in a static field will not make the instance members thread-safe. If you are going to be accessing it from multiple threads, you need to protect every access with a lock - either a lock/SyncLock block, a ReaderWriterLock/ReaderWriterLockSlim, or one of the other synchronization primitives defined in the System.Threading namespace.


    [1] http://odetocode.com/Blogs/scott/archive/2006/10/19/static-doesnt-mean-thread-safe.aspx

  • Richard,

    I corrected the article, thanks.

  • Can u provide this example for download as a ASP.Net solution. Will help us greatly to test a lot of situations.

    thanks

  • There isn't really enough code to make it worthwhile.

    You should be able to copy and paste the code.

    To avoid the stripping of carriage returns and linefeeds:

    First paste the code into WordPad, then copy and paste from WordPad into Visual Studio.

  • very good article.
    Thanks

  • good one...
    but the code is stripped!!!

  • "but the code is stripped!!!"

    I don't know what that means.

Comments have been disabled for this content.