views:

73

answers:

1

I have this code and I want to keep it elegant.
I got stuck at this inheriting issue and I would have to mess up the code if I do.
Help me keep it elegant. I don't mind making changes anywhere up and down the hierarchy; feel free to change the core.

I have these abstract classes (I omitted unrelated implementation to keep the question content short).

public abstract class Entity : IComparable 
{ 
    protected int _ID;
    public abstract int ID { get; } 
}    
public abstract class SortedEntities<T> : IEnumerable<T> where T : Entity 
{ 
    Dictionary<int,T> _Entities;
}

And, obviously, an example of inheritance is as follows:

public class Contact : Entity { }
public class Contacts : SortedEntities<Contact> { }

And I also have more than just Contact and Contacts that inherit from Entity and SortedEntities that all act in the same manner.

At some point in my app, I want to select entities based on and ID list.
A sample code of what I want is:

Contacts LoadedContacts = new Contacts();  // load and fill somewhere else
// !!!NOT IMPLEMENTED YET!!!
Contacts SelectedContacts = LoadedContacts.GetFromIDList("1,4,7");

Where it returns a new Contacts object filled with those Contact objects of the ID's provided.
So, to allow that code for all classes inheriting from SortedEntities, I thought of adding this imaginative code to the abstract:

public abstract class SortedEntities<T> : IEnumerable<T> where T : Entity 
{
    // !!!NOT REAL CODE YET!!!
    public abstract this<T> GetFromIDList(string IDCSV)
    {
        List<string> idlist = IDCSV.Split(',').ToList<string>();
        return this.Where(entity => idlist.Contains(entity.ID.ToString()));
    }
}

But obviously the this<T> is not allowed.
What I'm trying to tell the compiler is to make the return type of this method that of the inheriting class down the hierarchy.
That way, if someone calls LoadedContacts.GetFromIDList("1,4,7") it will return Contacts without having to cast it from SortedEntities<T> if I make it the return type, which would also require me to override the method in each inheriting class to hide the abstract method.

Am I forgetting something I already know?
Or is this completely not possible and I have to override and hide the method down the hierarchy for all inheriting classes?

+1  A: 

A common solution is to add another generic type parameter that refers to the "current" type (like this refers to the "current" object):

public abstract class Entity : IComparable
{
    protected int _ID;
    public abstract int ID { get; }
}

public abstract class SortedEntities<TEntities, TEntity> : IEnumerable<TEntity>
    where TEntities : SortedEntities<TEntities, TEntity>, new()
    where TEntity : Entity
{
    Dictionary<int, TEntity> _Entities;

    public TEntities GetFromIDList(string IDCSV)
    {
        List<string> ids = IDCSV.Split(',').ToList<string>();
        return new TEntities
        {
            _Entities = this.Where(entity => ids.Contains(entity.ID.ToString()))
                            .ToDictionary(e => e.ID)
        };
    }
}

Usage:

public class Contact : Entity
{
}

public class Contacts : SortedEntities<Contacts, Contact>
{
}

Note how the TEntities is restricted to a SortedEntities<TEntities, TEntity>. This does not really mean that TEntities can only refer to the current class, but as long as you follow the pattern of letting class X inherit from SortedEntities<X, Y>, it should work.

The new() constraint is required so you can instantiate a new instance of the "current" class.

Eric Lippert has indicated somewhere else that he dislikes this pattern. Act at your own risk!

dtb
I worry about it because it seems fragile. The example you are probably thinking of is class Animal<T> : where T:Animal<T> { public virtual void M(T t) {}}. You might think that this means that class Cat:Animal<Cat> has a method M that takes a Cat. But nothing stops you from saying Dog:Animal<Cat>, and now Dog has a method M that takes a Cat. If you are trying to represent the invariant that M on a class X that extends A<T> always takes an X, you cannot do that in the C#/CLR type system; it is simply not powerful enough to represent that concept, so *don't try*.
Eric Lippert
dtb, awesome answer! Perfect! With all due respect, Eric, I will still use it. I totally agree with you that nothing stops you from making that Dog:Animal<Cat> class that essentially makes no sense, but that itself is a reason to still use it, because it makes no sense otherwise. I will give the answer to dtb, but I still ask you, Eric, if I had the chance to completely change this approach, how would I do it?
Beemer
The basic question is: how would you implement an abstract class's method so that the inheriting classes don't have to create their own copies simply to make the return type their own type? It's tedious to create a method for EACH inheriting class for EACH method that needs to propagate down.
Beemer
@Eric Lippert: Thanks for restating your concerns; I wasn't able to find your comments from our discussion while ago. I'm also curious: How would you design a class hierarchy that solves the above problem?
dtb