Click here to Skip to main content
15,881,204 members
Articles / Programming Languages / C#

Collection Behaviors

Rate me:
Please Sign up or sign in to vote.
4.97/5 (7 votes)
26 Nov 2017CPOL8 min read 7.5K   66   4  
I describe reusable implementation of the collection behaviors that make items behave in a certain way as long as the items belong to a collection.

Introduction

Behaviors as a pattern were first introduced for WPF by MS Expression Blend team (at least according to my knowledge). In spite of this, the behaviors do not have anything to do with the visuals. In fact, I gave several examples of the non-visual behaviors in some of my previous articles. For example, View-View Model based WPF and XAML Implementational Patterns talk about the purely non-visual "single selection" and "two last items" selection behaviors.

In general, behavior is an object that can modify the behavior of a class non-invasively, i.e., it does not require any coding changes to the class.

WPF is very rich with events and behaviors can be very useful there, but I came across a lot of cases where the behaviors are also useful for non-WPF object, including View Models.

The way the behavior works is - when it is attached to an object, it makes some modifications to the object including creating the handlers for some of the object's events. These handlers are what creates the object's behavior change. When the behavior is detached from the object, its event handlers should be removed from the object's event.

Based on the above statement, the simplest behavior will provide a single event handler on attaching to an object and remove it on detaching.

Behaviors can be stateless or with a state:

  1. Stateless behaviors are not aware of a specific object they are attached to - in fact, you can have only one static behavior object that is used to modify behavior of every object that needs it.
  2. Behaviors with the state keep a reference to the object they are modifying. Correspondingly each modified object has to have its own behavior.

Collection behaviors allow to attach a behavior to every item within the collection. The trick is to keep the behavior attached to every item that belongs to a collection and to remove it from all the items that are removed from a collection or whenever the collection is replaced.

Here is the layout of the article:

  1. I start this article with a refresher on what single item behavior is, providing a very simple example.
  2. Then I show how to attach such behavior for a collection of items so that it is attached to every item if and only if the item is part of the collection and detached if item is removed from a collection.
  3. Then I show how to achieve the same with much less code, by employing the DoForEachItemCollectionBehavior class.
  4. Finally, I show how to further improve the code using behaviors disposable token. In particular, it shortens the collection behavior syntax, allows chaining the behavior and also allows to easily attach the behaviors outside of the collection containing class.

Code Location

You can download the code from the article or from the following Github link: Code for Collection Behavior Article.

All the testing solutions are located under TESTS folder.

Example of a Single Item Behavior

Solution TESTS/NP.Tests.SingleItemBehaviorTest/NP.Tests.SingleItemBehaviorTest.sln contains an example of PrintNotifiablePropertyBehavior that will work on a single item.

Program.Main() method contains the usage code:

C#
static void Main(string[] args)
{
    // create the test class
    MyNotifiablePropsTestClass myTestClass = new MyNotifiablePropsTestClass();

    // create the behavior
    PrintNotifiablePropertyBehavior printNotifiablePropertyBehavior = 
        new PrintNotifiablePropertyBehavior();

    // attach the behavior to the class
    printNotifiablePropertyBehavior.Attach(myTestClass);

    // should print (since Behavior is attached)
    myTestClass.TheString = "Hello World";

    // detach the behavior from the class
    printNotifiablePropertyBehavior.Detach(myTestClass);

    // should not print (since Behavior is detached);
    myTestClass.TheString = "Bye World";
}  

We create an object of class MyNotifiablePropsTestClass containing notifiable property TheString. We attach the PrintNotifiablePropertyBehavior whose purpose is to print the notifiable property name and value to the console when the property changes. We change the TheString property to "Hello World" string and, since the the behavior is attached, it prints...

C#
TheString: Hello World  

...to the console.

Then, we detach the behavior from the object:

C#
// detach the behavior from the class
printNotifiablePropertyBehavior.Detach(myTestClass);    

and change TheString property to "Bye World":

C#
// should not print (since Behavior is detached);
myTestClass.TheString = "Bye World";  

This change is not be printed since the property was detached.

The implementation of MyNotifiablePropsTestClass is also very simple - it implements INotifiablePropertyChanged interface and has only one notifiable property TheString:

C#
#region TheString Property
private string _str;
public string TheString
{
    get
    {
        return this._str;
    }
    set
    {
        if (this._str == value)
        {
            return;
        }

        this._str = value;
        this.OnPropertyChanged(nameof(TheString));
    }
}
#endregion TheString Property  

PringNotifiablePropertyBehavior simply attaches a handler to INotifiablePropertyChanged.PropertyChanged event that (using reflection) extract the property value and prints the property name and value to the console:

C#
public class PrintNotifiablePropertyBehavior :
    IStatelessBehavior<INotifyPropertyChanged>
{
    // the handler
    private static void NotifyiableObject_PropertyChanged
    (
        object sender, 
        PropertyChangedEventArgs e
    )
    {
        // pring property name and value to the console
        // using reflection
        sender.PrintPropValue(e.PropertyName);
    }

    public void Attach(INotifyPropertyChanged notifyiableObject)
    {
        // add the handler
        notifyiableObject.PropertyChanged +=
            NotifyiableObject_PropertyChanged;
    }

    public void Detach(INotifyPropertyChanged notifyiableObject)
    {
        // remove the handler
        notifyiableObject.PropertyChanged -= 
            NotifyiableObject_PropertyChanged;
    }
} 

Collection Behavior

Now assume that we want to achieve a similar behavior for every item in some ObservableCollection. The items that belong to the collection should have this behavior attached to them, the items that are removed from a collection (or if the whole collection is overridden as property in some containing class) the behavior should be detached from the items. All of this should happen automatically without any extra coding outside of the class that contains the collection.

Such example is given in TESTS/NP.Tests.ItemsCollectionTest/NP.Tests.ItemsCollectionTest.sln project.

MyNotifiableCollectionTestClass contains property TheCollection which is an ObservableCollection of items of MyNotifiablePropsTestClass (described in the previous section).

Here is the testing code (from Program.Main() method of the project):

C#
// create the observable collection containing class
MyNotifiableCollectionTestClass collectionTestClass = 
    new MyNotifiableCollectionTestClass();

// create item 1.
MyNotifiablePropsTestClass item1 = new MyNotifiablePropsTestClass();

// set TheCollection property of the collection
// containing class to be an ObservableCollection
// that contains item1
collectionTestClass.TheCollection = 
    new ObservableCollection<MyNotifiablePropsTestClass>
    (
        new MyNotifiablePropsTestClass[] { item1 }
    );

// change item1.TheString
// since item1 is part of the collection,
// it should print to console: 
item1.TheString = "Item1: Hello World";

// create item2
MyNotifiablePropsTestClass item2 = new MyNotifiablePropsTestClass();

// add item2 to the collection
collectionTestClass.TheCollection.Add(item2);

// change item2.TheString
// since item2 is part of the collection,
// it should print to console
item2.TheString = "Item2: Hello World";

// remove item2 from collection
collectionTestClass.TheCollection.RemoveAt(1);

// since item2 is no longer
// part of the collection
// should NOT print to console
item2.TheString = "Item2: Bye Wordl";

// disconnect the whole collection (i.e. Item1)
collectionTestClass.TheCollection = null;

// since the collection property is null,
// nothing should be printed to console
// when Item1.TheString is changed.
item1.TheString = "Item1: Bye World";  

When running the code, the following should be printed to the console:

C#
TheString: Item1: Hello World
TheString: Item2: Hello World  

"Bye World" messages should not be printed since when they are changed, the items are not part of the MyNotifiableCollectionTestClass.TheCollection property.

In order to achieve that, we provide some extra plumbing within MyNotifiableCollectionTestClass.

We add to it methods SetItems and UnsetItems which accept a collection of items and add or remove the handler to their PropertyChanged event:

C#
// removes the PropertyChanged handler from all
// old items
void UnsetItems(IEnumerable items)
{
    if (items == null)
        return;

    foreach (MyNotifiablePropsTestClass item in items)
    {
        item.PropertyChanged -= Item_PropertyChanged;
    }
}

// attached the PropertyChanged handler to all 
// new items
void SetItems(IEnumerable items)
{
    if (items == null)
        return;

    foreach(MyNotifiablePropsTestClass item in items)
    {
        item.PropertyChanged += Item_PropertyChanged;
    }
} 

Then the handler for the CollectionChanged event of the ObservableCollection will call UnsetItems on the old items and SetItems on the new items:

C#
private void _collection_CollectionChanged
(
    object sender, 
    NotifyCollectionChangedEventArgs e
)
{
    // remove handlers from the old items
    UnsetItems(e.OldItems);

    // add handlers to all new items
    SetItems(e.NewItems);
}  

Finally, within the setter of TheCollection property, we set the CollectionChanged handler and we also call UnsetItems on the old collection and SetItems on the new collection (passed by the value):

C#
private ObservableCollection<MyNotifiablePropsTestClass> _collection;
public ObservableCollection<MyNotifiablePropsTestClass> TheCollection
{
    get
    {
        return this._collection;
    }
    set
    {
        if (this._collection == value)
        {
            return;
        }

        if (_collection != null)
        {
            // remove the handler
            // from the old collection
            _collection.CollectionChanged -=
                _collection_CollectionChanged;
        }

        // remove handlers from items 
        // in the old collection
        UnsetItems(this._collection);

        this._collection = value;

        // add handlers to the items 
        // in the new collection
        SetItems(this._collection);

        if (_collection != null)
        {
            // watch for the new collection change
            // to set the added and unset
            // the removed items
            _collection.CollectionChanged +=
                _collection_CollectionChanged;
        }
    }
}  

You can see that in order to make the class with the collection to behave the way we want, we had to add a significant amount of code.

In the next section, I'll show how this amount of extra code can be drastically shrunk by employing a reusable DoForEachItemCollectionBehavior<T> class.

Achieving Collection Behavior with DoForEachItemCollectionBehavior<T> Class

Solution TESTS/NP.Tests.ItemsCollectionBehaviorTest/NP.Tests.ItemsCollectionBehaviorTest.sln shows the great simplification of the code. The expected behavior and the Program.Main(...) method are exactly the same as in the prevoius section; the change is with the plumbing inside MyNotifiableCollectionTestClass class. You can see that the class has shrunk by almost 50% - from 103 to 54 lines of code.

Here, the only extra code is to define the behavior and to attach and detach the behavior from the collection within the setter:

C#
// define the behavior.
DoForEachItemCollectionBehavior<INotifyPropertyChanged>
    _doForEachItemCollectionBehavior =
        new DoForEachItemCollectionBehavior<INotifyPropertyChanged>
        (
            item => item.PropertyChanged += Item_PropertyChanged,
            item => item.PropertyChanged -= Item_PropertyChanged
        ); 

And here is the code for TheCollection property getter and setter:

C#
private ObservableCollection<MyNotifiablePropsTestClass> _collection;
public ObservableCollection<MyNotifiablePropsTestClass> TheCollection
{
    get
    {
        return this._collection;
    }
    set
    {
        if (this._collection == value)
        {
            return;
        }

        // detach from old collection
        _doForEachItemCollectionBehavior.Detach(_collection);

        this._collection = value;

        // attach to new collection
        _doForEachItemCollectionBehavior.Attach(_collection);
    }
}  

Further Improvement of the Code by Using Disposable Tokens

There is a way to further sharing the code, by employing Disposable Behaviors Tokens and extension methods of NP.Paradigms.Behaviors.DoForEachBehaviorUtils static class.

Take a look at TESTS/NP.Tests.ItemsCollectionDisposableBehaviorTest/NP.Tests.ItemsCollectionDisposableBehaviorTest.sln solution.

Again the Program.Main(...) method is exactly the same as in the previous two examples.

MyNotifiableCollectionTestClass, however, is even smaller than in the prevous sample - only 52 lines.

All we need to do is to add the following class field:

// Contains the disposable token.
// Calling its Dispose() method will
// Detach all the behaviors from the collection IDisposable _disposableBehaviors;

and to add the following code to the end of the setter:

C#
_disposableBehaviors?.Dispose(); // detaches old behaviors
_disposableBehaviors = _collection.AddBehavior
(
    item => item.PropertyChanged += Item_PropertyChanged,
    item => item.PropertyChanged -= Item_PropertyChanged
);  

Here, we create the behavior or even a behavior collection when the collection changes by calling DoForEachBehaviorUtils.AddBehavior extension method. The method returns an IDisposable that will contain all the created behaviors and will detach them when IDisposable.Dispose() method is called.

Those who know Rx.NET, will recognize that this way of unsubscribing was borrowed from there.

By now, you might be wondering why I am saying that multiple behaviors might be detached on the token disposal even though only one behavior was created. The thing is that this method (DoForEachBehaviorUtils.AddBehavior) also allows us to chain multiple behaviors as will be shown in the next section.

Chaining Multiple Collection Behaviors Outside of the Class that Defines the Collection

Code for this example is located under TESTS/NP.Tests.ItemsCollectionDisposableChainedBehaviorTest/NP.Tests.ItemsCollectionDisposableChainedBehaviorTest.sln solution.

The example shows how to chain multiple behaviors using AddBehavior extension method and also how attach the behaviors to a collection outside of the class that defines it using the disposable tokens paradigm.

There are some changes to the test object. First of all, the MyNotifiableCollectionTestClass now defines an event TheCollectionValueChangedEvent event which fires when the value of TheCollection property changes (within the properties setter):

C#
public event Action<ObservableCollection<MyNotifiablePropsTestClass>> 
    TheCollectionValueChangedEvent = null;

#region TheCollection Property
private ObservableCollection<MyNotifiablePropsTestClass> _collection;
public ObservableCollection<MyNotifiablePropsTestClass> TheCollection
{
    get
    {
        return this._collection;
    }
    set
    {
        if (this._collection == value)
        {
            return;
        }

        this._collection = value;

        // fire collection changed event:
        TheCollectionValueChangedEvent?.Invoke(_collection);
    }
}
#endregion  

The disposable token and setting up the behavior is moved over to Program.Main(...).

Static Program class now has a static property NumberItemsInCollection. The purpose of the second behavior within the chain is to maintain this property to match the number of items within collectionTestClass.TheCollection collection. The setter of this property also sends the string containing the property value to console:

C#
static int _numberItemsInCollection = 0;
public static int NumberItemsInCollection
{
    get
    {
        return _numberItemsInCollection;
    }
    set
    {
        if (_numberItemsInCollection == value)
            return;

        _numberItemsInCollection = value;

        // write the changed property to console
        Console.WriteLine($"Number items in collection: {_numberItemsInCollection}");
    }
}  

At the very top of Program.Main(...) method's body, after the collectionTestClass is created, we add a handler to its TheCollectionValueChangedEvent:

C#
// create the observable collection containing class
MyNotifiableCollectionTestClass collectionTestClass = 
    new MyNotifiableCollectionTestClass();

// create the handler for event fired when TheCollection
// property is assigned a new value
collectionTestClass.TheCollectionValueChangedEvent +=
    CollectionTestClass_TheCollectionValueChangedEvent;  

The rest of Program.Main(...) body is exactly the same as in previous 3 examples.

Here is the CollectionTestClass_TheCollectionValueChangedEvent handler:

C#
static IDisposable _disposableBehaviors = null;
private static void CollectionTestClass_TheCollectionValueChangedEvent
(
    ObservableCollection<MyNotifiablePropsTestClass> collection
)
{
    _disposableBehaviors?.Dispose();

    // chain the two behaviors - one to record the 
    // changed property value, the other to maintain
    // NumberItemsInCollection of the same size as 
    // the collection.
    _disposableBehaviors = collection
        .AddBehavior
        (
            item => item.PropertyChanged += Item_PropertyChanged,
            item => item.PropertyChanged -= Item_PropertyChanged
        )
        .AddBehavior
        (
            item => NumberItemsInCollection++,
            item => NumberItemsInCollection--
        );
}  

Note that there is only one disposable token (_disposableBehaviors) even though we can chain as many behaviors as we want.

Also, note, that we could also use Attach/Detach approach outside of the class in which the collection is defined, but we would need to have two events - one indicating that the old collection is removed and one indication that the new collection is created or perhaps one event with two argument - for old and new collections, which is more complex that what we have with disposable token.

When you run this sample, you should get:

Number items in collection: 1
TheString: Item1: Hello World
Number items in collection: 2
TheString: Item2: Hello World
Number items in collection: 1
Number items in collection: 0  

Conclusion

In this article, I describe the reusable functionality that absorbs the complexity of creating behaviors for collection items as long as they are part of a collection. When the items are added to a collection, the behaviors are attached to them and when the items are removed from a collection, the behaviors are detached from them. Please enjoy the code!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect AWebPros
United States United States
I am a software architect and a developer with great passion for new engineering solutions and finding and applying design patterns.

I am passionate about learning new ways of building software and sharing my knowledge with others.

I worked with many various languages including C#, Java and C++.

I fell in love with WPF (and later Silverlight) at first sight. After Microsoft killed Silverlight, I was distraught until I found Avalonia - a great multiplatform package for building UI on Windows, Linux, Mac as well as within browsers (using WASM) and for mobile platforms.

I have my Ph.D. from RPI.

here is my linkedin profile

Comments and Discussions

 
-- There are no messages in this forum --