I Dream Of Whidbey: Generics and Anonymous Methods

We (the team at iP3) are implementing an object model using Whidbey at the moment, and we have been copying and pasting a lot of code lately which is one of the things that I try to minimise as much as possible. One method that we have copied a lot is a simple (and un-optimised) ‘add’ method that helps maintain the many side of a one-to-many relationship.

After creating a method about a week ago that used Generics to allow greater code reuse, I thought that I might be able to use a combination of Generics and Anonymous Methods to allow even more code reuse.

My goal was to create an ‘add’ method that used Generics and Anonymous Methods and could sit in an external class.

The results are below; ClassA and ClassB are the classes that have the relationship, RelationshipHelper is the class with the add method, DelegateTests is the test fixture I created to support me while I refactored the methods for about an hour ;).

I had one stumbling point during this when trying to set the Contains array on ClassA. It would compile just fine, but would fail the test (thank goodness I had a quick and simple test) with a NullReferenceException. It turns out that the anonymous method needed to call a method (yes, even just a private method) to set the _contains member variable. Weird…

It is interesting to note that the standard method size is 266 characters and the much less readable method that used Anonymous Methods and Generics is 227 characters. I think we’ll have to think about whether we want to encourage readability or code reuse with this. I’m leaning towards the standard method because of the readability, but it is great to have implemented this once so we can use it again if a code reuse opportunity arises in the future.

Enjoy!

ClassA.cs

using System;

using System.Collections.Generic;

namespace Model.Common

{

      /// <summary>

      /// Example for Generics and Delegates

      /// ClassA contains many ClassBs

      /// ClassB is contained in one ClassA

      /// </summary>

      public class ClassA

      {

            private ClassB[] _contains;

            public ClassB[] Contains

            {

                  get

                  {

                        return _contains;

                  }

            }

            private void SetContains(ClassB[] objects)

            {

                  _contains = objects;

            }

            public ClassA()

            {

            }

            /// <summary>Standard add method</summary>

            public void AddClassB1(ClassB instanceOfB)

            {

                  List<ClassB> list = new List<ClassB>();

                  if (this.Contains != null)

                  {

                        foreach(ClassB item in this.Contains)

                        {

                              list.Add(item);

                        }

                  }

                  if (!list.Contains(instanceOfB))

                  {

                        list.Add(instanceOfB);

                        this._contains = list.ToArray();

                        instanceOfB.ContainedIn = this;

                  }

            }

            /// <summary>Add method using Generics and Anonymous Methods</summary>

            public void AddClassB2(ClassB instanceOfB)

            {

                  RelationshipHelper.AddToMany<ClassB>(

                        instanceOfB,

                        delegate()

                              {return this.Contains;},

                        delegate(ClassB[] classBs)

                              {this.SetContains(classBs);},

                        delegate(ClassB classB)

                              {classB.ContainedIn = this;});

            }

      }

}

ClassB.cs

namespace Model.Common

{

      public class ClassB

      {

            private ClassA _containedIn = null;

            public ClassA ContainedIn

            {

                  get { return _containedIn; }

                  set

                  {

                        if (_containedIn != value)

                        {

                              _containedIn = value;

                        }

                  }

            }

            public ClassB()

            {

            }

      }

}

RelationshipHelper.cs

using System;

using System.Collections.Generic;

namespace Model.Common

{

      /// <summary>

      /// Summary description for RelationshipHelper.

      /// </summary>

      public class RelationshipHelper

      {

            public delegate T[] GetManyArray<T>();

            public delegate void SetManyArray<T>(T[] objects);

            public delegate void SetSingleSide<T>(T instance);

            public static void AddToMany<T>(

                        T instanceToAdd,

                        GetManyArray<T> getManyArray,

                        SetManyArray<T> setManyArray,

                        SetSingleSide<T> setSingle)

            {

                  List<T> list = new List<T>();

                  if (getManyArray() != null)

                  {

                        foreach (object item in getManyArray())

                        {

                              list.Add((T)item);

                        }

                  }

                  if (!list.Contains(instanceToAdd))

                  {

                        list.Add(instanceToAdd);

                        setManyArray(list.ToArray() as T[]);

                        setSingle((T)instanceToAdd);

                  }

            }

      }

}

DelegateTests.cs

using System;

using NUnit;

using NUnit.Framework;

using Model.Common;

namespace UnitTests.ModelTests

{

      [TestFixture]

      public class DelegateTests

      {

            [Test]

            public void TestStandardAssociationMethod()

            {

                  ClassA a = new ClassA();

                  ClassB b1 = new ClassB();

                  a.AddClassB1(b1);

                  ClassB b2 = new ClassB();

                  a.AddClassB1(b2);

                  Assertion.AssertEquals(2, a.Contains.Length);

                  Assertion.AssertEquals(a, b1.ContainedIn);

                  Assertion.AssertEquals(a, b2.ContainedIn);

            }

            [Test]

            public void TestDelegateAssociationMethod()

            {

                  ClassA a = new ClassA();

                  ClassB b1 = new ClassB();

                  a.AddClassB2(b1);

                  ClassB b2 = new ClassB();

                  a.AddClassB2(b2);

                  Assertion.AssertEquals(2, a.Contains.Length);

                  Assertion.AssertEquals(a, b1.ContainedIn);

                  Assertion.AssertEquals(a, b2.ContainedIn);

            }

      }

}