tags:

views:

89

answers:

2

I am working on a class library and am having some trouble with generics. I have a ITransaction interface which has a collection of ITransactionItem. Each ITranscation can be either a CapitalCall or Distribution. A CapitalCall is a ITransaction but has a few additional properties. A CapitalCallItem is a ITransactionItem with a few additional properties. A CapitalCall has a collection of CapitalCallItems. Likewise, there exists a Distribution class with a collection of DistributionItem.

I have tried making the Transaction interface generic:

interface ITransactionBase<TItem> 
    where TItem: ITransactionItem
{
    List<TItem> ITransactionItems
    {
        get;
        set;
    }

}

This works perfectly when I implement it:

class CapitalCall : ITransactionBase<CapitalCallItem>

Now all of the items in the collection are of type CapitalCallItem.

I run into the following problem. I would like to know the associate ITransaction on a ITranscationItem. I created a property on the ITransactionItem table of type ITranscation. When I use this property, it is no longer typed to the correct class:

var capitalCall = new CapitalCall();
var trans = capitalCall.TransactionItems[0].Transaction;
// trans is now of the base type ITransaction, instead of typed to CapitalCall.

I have tried making the ITransactionLineItem interface use generics as well, but I get into a recursive generic nightmare when I try to declare it. What is the correct way to model this?

Would this work:

interface ITransaction<TAction, TItems>
    where TItems : ITransactionItem<TAction, TItems>
    where TAction : ITransaction<TAction, TItems>

interface ITransactionItem<TAction, TItems>
    where TItems : ITransactionItem<TAction, TItems>
    where TAction : ITransaction<TAction, TItems>

I am confused as to how I could then use the interface by itself- what if I want a collection of mixed ITransactionItem, without specifying a type? Also I should add that I have base Transaction / Transaction item classes that implement the interface, and CapitalCall / Dist inherit from.

+1  A: 

Yes the interfaces you wrote down should work as far as I can tell. Such "recursive" declarations work well with generics, but the question is whether you really need to make those generic in the first place? Recursive declarations are something which is not often used and may therefore be hard to grasp for other people using your classes.

As for using the interface for itself, you can still make a less generic interface and also implement it.

Lucero
+1  A: 

Yes, this sort of mutually recursive generic declaration will work, but it will make things very complicated - I know from experience. If you want an example of something similar, look at this declaration from my protocol buffers port:

public interface IMessage<TMessage, TBuilder> : IMessage<TMessage>
     where TMessage : IMessage<TMessage, TBuilder> 
     where TBuilder : IBuilder<TMessage, TBuilder>

IBuilder<,> has the equivalent.

This declaration also demonstrates the answer to your last question: if some parts of your interface don't need to know the exact type of transaction, you can declare them in a "less generic" base interface. So you could have:

interface ITransaction<TAction, TItems> : ITransaction
    where TItems : ITransactionItem<TAction, TItems>
    where TAction : ITransaction<TAction, TItems>

for example, where ITransaction is a non-generic interface.

Again though, this is not for the faint of heart. In my case I can get away with it because almost no-one uses the raw interfaces - all the implementations are autogenerated, and client code uses those non-generic implementations. I would think long and hard before inflicting this on a developer to actually use day to day...

Jon Skeet
Thanks I think this answered my question. I am just leaving the generic declaration out of ITransactionItem- I can just add a CapitalCall property to CapitalCallItem that returns the Transaction property staticly typed. However, this is a bit of a shame really because I love inflicting pain on fellow developers.
Shawn Simon