Extension Dilema

I was just working on a little Open Source project I have where I need an un-directed graph to represent relationships between people who have and have not met. I have also been working on a little open source project that supplies basic graph algorithms (which can be found here). I figured the best course of action would be to import the library and extend the node class to represent a person. Then I could add my own AddNeighbor and RemoveNeighbor methods. Everything went well until time to run unit tests and I found something peculiar.

The original implementation of Node is very generic and is as follows:

 public class Node<T> 
    {
        public List<Node<T>> Neighbors { get; private set; }

        public T Data { get; private set; }

        public Node(T data)
        {
            if (data == null) throw new ArgumentNullException("data");
            Data = data;
            Neighbors = new List<Node<T>>();
        }

        public void AddNeighbor(Node<T> node)
        {
            if (node == null) throw new ArgumentNullException("node");
            if (!Neighbors.Contains(node))
            {
                Neighbors.Add(node);
            }
        }
    }

The implementation was as easy as adding a RemoveNeighbor method to the Node class:

public void RemoveNeighbor(Node<T> node)
        {
            if (node == null) throw new ArgumentNullException("node");
            if (Neighbors.Contains(node))
            {
                Neighbors.Remove(node);
            }
        }

At this point I could extend Node and write my own add and remove neighbor methods:

public class Person : Node<int>
    {
        public Person(int data) : base (data){}

        public void RemoveNeighbor(Person person1, Person person2)
        {
            person1.RemoveNeighbor(person2);
            person2.RemoveNeighbor(person1);
        }

        public void AddNeighbor(Person person1, Person person2)
        {
            person1.AddNeighbor(person2);
            person2.AddNeighbor(person1);
        }
    }

For a first try I was very pleased with my efforts, but when I went to implement other functionality in the application, I noticed that the method signatures were clunky. I wanted to do something like this:

person1.AddNeighbor(person2);
person1.RemoveNeighbor(person2);

So to implement that, I added two extension methods that looked like the following:

        public static void RemoveNeighbor(this Person person1, Person person2)
        {
            person1.RemoveNeighbor(person2);
            person2.RemoveNeighbor(person1);
        }

        public static void AddNeighbor(this Person person1, Person person2)
        {
            person1.AddNeighbor(person2);
            person2.AddNeighbor(person1);
        }

If you already can see what is going to happen, this compiles just fine. These are valid overloads of the methods. Unfortunately, the compiler assumes that I want the Node’s implementation when I use it as demonstrated as above, since the signatures in the consumer are equivalent, even though I know I want to use the extension methods, so unit tests start failing.

The only way I can see around this is for me to name my extension methods differently. I would be interested to hear from someone who knows how to specify which method to use on the consumers side given that the method signatures are equivalent.
 

Advertisements

3 thoughts on “Extension Dilema

  1. Another solution is to have Person contain a Node instead of inheriting from Node (composition over inheritance). Then instead of having extension methods, let the Person class define AddNeighbor and RemoveNeighbor and implement them like the extension methods.

    This is an interesting design problem, so thanks for sharing.

  2. Pingback: Professional Development – 2015 – Week 28

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s