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.
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:
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:
for example, where
ITransactionis 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…