C# – How to avoid having nested generic in class

cclass-designinheritance

I'm working on a side project, and I turned on all rules for code analysis in Visual Studio, and I got the warning notice:

Warning CA1006 Consider a design where 'Vote<T>.CalculateWinner(List<Vote<T>>)' doesn't nest generic type 'List<Vote<T>>'

And it really does point out a sore spot in my design. I have an abstract base class Vote<T> that contains a list of the options for running an election on type T.

Here is what I want to exist:

There are three types of objects, PolicyCategory, Policy and Funding. A PolicyCategory contains a list of votes on the policies within it, and a vote on a Policy must also contain a list of votes on a Funding object. The election procedure (happens to be STV) is then run on the votes in the category, and then on the votes on the winning policy. The procedure is the same for both types of votes.

What I have now is PolicyVote : Vote<Policy> and FundingVote : Vote<Funding>, the core election logic is in Vote<T>, and I have methods that cast List<PolicyVote> to List<Vote<Policy>>, and then call Vote<T>.CalculateWinner().

This is clearly pretty horrible as I'm hacking around the lack of contravariance in C#.

How can this be made better?

Best Answer

I would consider using an interface for Vote instead of generics.

That way if you had something like:

interface IVote {
    // your interface requirements
}

And then on your class definitions implement the IVote interface:

class PolicyVote : IVote {
    // class def
}

class FundingVote : IVote { 
    // class def
}

//etc. 

You could then avoid casting to Vote<T> and use IVote as a type instead and any class that implements IVote will be recognised by the compiler as type safe :

public static int CalculateWinner(List<IVote> votes) {
    // your calc logic
}

//You can now pass any object that implements `IVote` to the method
//assuming policyVotes and fundingVotes are type List<IVote>
CalculateWinner(policyVotes); // this will work
CalculateWinner(fundingVotes); // so will this