0

I have a generic class with specified type of key and I want to create KeyedCollection(Of of that class. I think that code is more clear the my words.
The question is whether one can rewrite the code so it would be possible to define MyEnities class without specifying key type (int or integer)

C#

public abstract class Entity<TPrimKey>
{public abstract TPrimKey Key { get; set; }    }

public class Entities<TPrimKey, TItem> : ObjectModel.KeyedCollection<TPrimKey, TItem> 
                       where TItem : Entity<TPrimKey>
{
    protected override TPrimKey GetKeyForItem(TItem item)
    {return item.Key;}
}

public class MyEntity : Entity<int>
{
    public override int Key { get; set; }
}

public class MyEnities : Entities<int, MyEntity>
{}

VB

Public MustInherit Class Entity(Of TPrimKey)
   Public MustOverride Property Key As TPrimKey
End Class

Public Class Entities(Of TPrimKey, TItem As Entity(Of TPrimKey))
    Inherits ObjectModel.KeyedCollection(Of TPrimKey, TItem)
    Protected Overrides Function GetKeyForItem(item As TItem) As TPrimKey
        Return item.Key
    End Function
End Class

Public Class MyEntity
    Inherits Entity(Of Integer)
    Public Overrides Property Key As Integer
End Class

Public Class MyEnities
    Inherits Entities(Of Integer, MyEntity)
End Class
IvanH
  • 5,039
  • 14
  • 60
  • 81

1 Answers1

0

You almost had it. Here is a solution:

using System.Collections.ObjectModel;

public class Entity<TPrimKey>
{
    public TPrimKey Key { get; set; }
}

public class Entities<TPrimKey> : KeyedCollection<TPrimKey, Entity<TPrimKey>>
{
    protected override TPrimKey GetKeyForItem( Entity<TPrimKey> item )
    { return item.Key; }
}

public class MyEntity1 : Entity<int>
{
}

public class MyEntity2 : Entity<int>
{
}

public class MyEntities : Entities<int>
{
    public static void Test()
    {
        var entities = new MyEntities();

        var entity1 = new MyEntity1();
        entities.Add( entity1 );

        var entity2 = new MyEntity2();
        entities.Add( entity1 );
    }
}

To be clear, this is a slightly different way of thinking about your goal. MyEntities still does specify key type int; any generic methods you want go into Entities<TPrimKey>. OR you create a generic class MyEntitiesA<TPrimKey> between Entities and MyEntities, to hold those methods:

public abstract class MyEntitiesA<TPrimKey> : Entities<TPrimKey>
{
    // Methods that apply to all TPrimKeys go here.
    // Some methods may need to be abstract, and implemented
    // in each subclass, that has a concrete key (or at least a key with a constraint).
}

public class MyEntities : MyEntitiesA<int>
...

MyEntitiesA<TPrimKey> satisfies your requirement without specifying key type int. (At some point, you will create classes and/or variable declarations that have a concrete type; the "goal" is to be able to stay generic for all methods that can be described generically.)

What you were missing is in this line:

    public class Entities<TPrimKey> : KeyedCollection<TPrimKey, Entity<TPrimKey>>

There, the type of value is defined in terms of the type of key. This allows you to define GetKeyForItem in such a way that it works for all TPrimKeys.

Note that I've included two classes deriving from Entity<int>, to show the full power of this collection. Also Test() to show it in action.


You might also find these links relevant:
"Generic constraint to match numeric types"
use "where TPrimKey : class"
"Blind Interfaces to Models"


This technique is even more powerful if base the collection on an interface instead of a base class. This allows any class which supports the correct interface (on that key type) to be added to the collection. Here, we see that this allows us to define generic classes EntityA<TPrimKey> and EntityB<TprimKey>, with various subclasses of those:

using System.Collections.ObjectModel;

public interface IEntity<TPrimKey>
{
    TPrimKey Key { get; set; }
}

public class Entities<TPrimKey> : KeyedCollection<TPrimKey, IEntity<TPrimKey>>
{
    protected override TPrimKey GetKeyForItem( IEntity<TPrimKey> item )
    { return item.Key; }
}

public class EntityA<TPrimKey> : IEntity<TPrimKey>
{
    public TPrimKey Key { get; set; }
}

public class MyEntityA1 : EntityA<int>
{
}

public class MyEntityA2 : EntityA<int>
{
}

public class EntityB<TPrimKey> : IEntity<TPrimKey>
{
    public TPrimKey Key { get; set; }
}


public class MyEntityB1 : EntityB<int>
{
}

public class MyEntities : Entities<int>
{
    public static void Test()
    {
        var entities = new MyEntities();

        var entityA1 = new MyEntityA1();
        entities.Add( entityA1 );

        var entityA2 = new MyEntityA2();
        entities.Add( entityA2 );

        var entityB1 = new MyEntityB1();
        entities.Add( entityB1 );
    }
}

NOTE: You don't have to go to interface to create multiple generic subclasses EntityA and EntityB. I'm just saying that if you start getting that complicated, its time to consider defining an interface, so that you don't get "boxed in" to a single base class. Defining an interface makes it easy to refactor your class hierarchy, if you ever need to.

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196