views:

210

answers:

2

(C#, VS2008) In a program I'm working on, I've got lots of objects that all have an ID and implement IComparable so that List<>-s of the various objects are easily searchable by ID. Since I hate copy/pasting code, I thought I'd abstract that bit of functionality down to a base class, like so:

using System;

namespace MyProg.Logic
{
    abstract class IDObject : IComparable<IDObject> 
    {
        private int miID;

        public int ID
        {
            get { return miID; }
            set { miID = value; }
        }

        public IDObject(int ID)
        {
            miID = ID;
        }

        #region IComparable<IDObject> Members

        int IComparable<IDObject>.CompareTo(IDObject other)
        {
            return miID.CompareTo(other.miID);
        }

        #endregion
    }
}

The drawback I see to that is that two separate classes that each inherit it would be directly comparable using .CompareTo() and I was hoping to enforce that each class that inherits from IDObject is only Comparable to others of the exact same class. So I was hoping to figure out how to do that and came up with this

using System;

namespace MyProg.Logic
{
    abstract class IDObject : IComparable<T> where T : IDObject
    {
        private int miID;

        public int ID
        {
            get { return miID; }
            set { miID = value; }
        }

        public IDObject(int ID)
        {
            miID = ID;
        }

        #region IComparable<T> Members

        int IComparable<T>.CompareTo(T other)
        {
            return miID.CompareTo(other.miID);
        }

        #endregion
    }
}

But that gives a compile error of "Constraints are not allowed on non-generic declarations"

Looking at it, I'm sure there's a way to do something like that so that each class is only comparable to other instances of that same class, but I can't tease out the syntax.

+8  A: 

You can use the Curiously Recurring Template Pattern to solve this problem.

abstract class Base<T> : IComparable<T> where T : Base<T> {
    public int Rank { get; set; } // Order instances of derived type T by Rank
    public int CompareTo(T other) { return Rank.CompareTo(other.Rank); }
}
class Foo : Base<Foo> {}
class Bar : Base<Bar> {}

static class Program {
   static void Main() {
       var foo1 = new Foo { Rank = 1 };
       var foo2 = new Foo { Rank = 2 };
       var bar1 = new Bar { Rank = 1 };
       var bar2 = new Bar { Rank = 2 };

       Console.WriteLine(foo1.CompareTo(foo2));
       Console.WriteLine(bar2.CompareTo(bar1));

       //error CS1503: Argument '1': cannot convert from 'Bar' to 'Foo'
       //Console.WriteLine(foo1.CompareTo(bar1));
   }
}
Nick Guerrera
+1  A: 

I think you've got bigger problems than just making sure that the derived class types are the same. You are also saddling the derived class with the responsibility to generate a unique ID. That requires the derived class to be aware what other IDs were assigned previously. Realistically, that requires a class factory. You'll need to enforce that by making the constructor of your abstract class protected.

Not very practical. If the ID is just an opaque number that establishes object identity then consider assigning the ID yourself. Use a static member to keep track of the last assigned one. Now it becomes simple, and you don't have to worry about derived class types anymore.

Hans Passant
+1. Good point. I've updated my answer to show the pattern while staying out of the ID issue.
Nick Guerrera
The ID's come from a SQLite DB Integer Primary Key column - So they guaranteed by SQLite to be unique among an object type, but two objects of different types could conceivably overlap, hence wanting to limit the IComparable to only members of the same class.
Drogo