Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make an Abstract Base class IComparable that doesn't compare two separate inherited classes?

(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.

like image 353
Drogo Avatar asked Jan 09 '10 06:01

Drogo


2 Answers

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));
   }
}
like image 177
Nick Guerrera Avatar answered Oct 18 '22 21:10

Nick Guerrera


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.

like image 39
Hans Passant Avatar answered Oct 18 '22 19:10

Hans Passant