Encapsulate Collection. Possible ReadOnlyBindingListWrapper<T>.

Encapsulate Collection. Possible ReadOnlyBindingListWrapper<T>.

Old forum URL: forums.lhotka.net/forums/t/4531.aspx


MozgC posted on Thursday, March 20, 2008

Dear Rockford,
ReadOnlyBindingList is a very good class. But sometimes you need to return read-only collection from a class.

For example, we have a ManufacturerAccessor class (inside DAL) that keeps collection of manufacturers internally. Its method GetAllManufacturers() should return a collection of manufacturers, but that collection should be read-only for external consumer. In this case it is very convenient to keep collection as BindingList, but return ReadOnlyBindingListWrapper.

Here is the possible implementation (based on your ReadOnlyBindingList):

///
/// A readonly version of BindingList(Of T)
///
/// Type of item contained in the list.
///
/// This is a subclass of BindingList(Of T) that implements
/// a readonly list, preventing changing, adding and removing of items
/// from the list.
///
public class ReadOnlyBindingListWrapper : BindingList
{
private const string MSG_NOT_ALLOWED_IN_READONLY_COLLECTION = "Not allowed in read-only collection";

///
/// Creates an instance of the object.
///
public ReadOnlyBindingListWrapper(BindingList list) : base(list)
{
this.RaiseListChangedEvents = false;
base.AllowEdit = false;
base.AllowRemove = false;
base.AllowNew = false;
this.RaiseListChangedEvents = true;

list.ListChanged += new ListChangedEventHandler(ReadOnlyBindingList_ListChanged);
}

private void ReadOnlyBindingList_ListChanged(object sender, ListChangedEventArgs e)
{
OnListChanged(e);
}

///
/// Prevents clearing the collection.
///
protected override void ClearItems()
{
throw new InvalidOperationException(MSG_NOT_ALLOWED_IN_READONLY_COLLECTION);
}

///
/// Prevents insertion of items into the collection.
///
protected override object AddNewCore()
{
throw new InvalidOperationException(MSG_NOT_ALLOWED_IN_READONLY_COLLECTION);
}

///
/// Prevents insertion of items into the collection.
///
/// Index at which to insert the item.
/// Item to insert.
protected override void InsertItem(int index, T item)
{
throw new InvalidOperationException(MSG_NOT_ALLOWED_IN_READONLY_COLLECTION);
}

///
/// Prevents removing of items into the collection
///
/// Index of the item to remove.
protected override void RemoveItem(int index)
{
throw new InvalidOperationException(MSG_NOT_ALLOWED_IN_READONLY_COLLECTION);
}

///
/// Prevents replacing the item at the specified index with the
/// specified item
///
/// Index of the item to replace.
/// New item for the list.
protected override void SetItem(int index, T item)
{
throw new InvalidOperationException(MSG_NOT_ALLOWED_IN_READONLY_COLLECTION);
}

///
/// Gets a value indicating whether items in the list can be edited.
///
public new bool AllowEdit { get { return false; } }

///
/// Gets a value indicating whether you can add items to the list using the AddNew method.
///
public new bool AllowNew { get { return false; } }

///
/// Gets a value indicating whether you can remove items from the collection.
///
public new bool AllowRemove { get { return false; } }
}

If somebody is gonna use it, don't forget to write BindingList or IList in signature of a method that returns that ReadOnlyBindingListWrapper. Here is the possible usage:

class ManufacturerAccessor { ...

private BindingList _manufacturers = new BindingList();

public BindingList GetAllManufacturers()
{
...
return new ReadOnlyBindingListWrapper(_manufacturers);
}

And you call it like this:

BindingList manufacturers = ManufacturerAcessor.GetAllManufacturers();

As it seems this forum doesn't support good code insertion, you can take source code in attachment.

MozgC replied on Sunday, March 23, 2008

Any comments?

tmg4340 replied on Sunday, March 23, 2008

MozgC:
Dear Rockford, ReadOnlyBindingList is a very good class. But sometimes you need to return read-only collection from a class. For example, we have a ManufacturerAccessor class (inside DAL) that keeps collection of manufacturers internally. Its method GetAllManufacturers() should return a collection of manufacturers, but that collection should be read-only for external consumer. In this case it is very convenient to keep collection as BindingList, but return ReadOnlyBindingListWrapper.

If you're worried about binding to a read-only collection, why not simply have your DAL return a DataReader, populate a standard ReadOnlyList with ReadOnly child objects, and use that?  You can bind to it just like an editable list, and by default it doesn't allow for adding or removing of items.  And if the child objects in the list are ReadOnly-derived objects, you can't modify them.

Aside from that, you shouldn't be mixing CSLA objects in your DAL - CSLA is designed to create business objects.  As such, it's designed to sit between your DAL and your UI.

- Scott

MozgC replied on Sunday, March 23, 2008

tmg4340:

If you're worried about binding to a read-only collection, why not simply have your DAL return a DataReader, populate a standard ReadOnlyList with ReadOnly child objects, and use that?  You can bind to it just like an editable list, and by default it doesn't allow for adding or removing of items.  And if the child objects in the list are ReadOnly-derived objects, you can't modify them.


Aside from that, you shouldn't be mixing CSLA objects in your DAL - CSLA is designed to create business objects.  As such, it's designed to sit between your DAL and your UI.


- Scott



DAL shouldn't return a DataReader, it should return business object to BLL. It's not BLL's task to work with low level data such as in DataReader. Also I need to know about changes in collection. For example, if I get that collection and connect it to combobox or whatever, I need to show updates if the collection is changed (new manufacturer added, manufacturer info updated and so on).

And I should mix business objects in my DAL - it IS ok, as my model is based on objects, not on plain row data. Only DAL works with plain data, all other layers work with business objects, so DAL returns business objects, not DataReader.

Copyright (c) Marimer LLC