3

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?

3
  • I don't understand, you can't cast List<PolicyVote> to List<Vote<Policy>>, do you mean you create a copy of the list? And C# does support variance: List<PolicyVote> can be cast to IEnumerable<Vote<Policy>> or IReadOnlyList<Vote<Policy>>. – svick Jan 31 '16 at 16:38
  • @svick When I say cast, I mean use a loop and cast all the members individually. – soandos Jan 31 '16 at 16:39
  • Shouldn't this question be on CodeReview or StackOverflow? – Luis Masuelli Mar 1 '16 at 17:35
1

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 
5
  • Doesn't this have the same contravariance problem? – soandos Jan 31 '16 at 16:40
  • How can I pass in a List<PolicyVote> and have it work? – soandos Jan 31 '16 at 19:58
  • @soandos - are you both iterating and modifying the list, or just iterating it? If the latter, you can cast the list to a more general type that doesn't allow mutation, which should then let you cast the contents to a supertype. List is only an issue because it allows both. – Jules Jan 31 '16 at 20:51
  • @Jules I am modifying the contents of the list (the individual votes), but not the list of votes itself. So the cast (List<IVote>)(votes) if votes is of type List<PolicyVote> is safe? – soandos Feb 1 '16 at 14:49
  • As svick pointed out earlier, you can cast to IEnumerable<superclass> or IReadOnlyList<superclass> from List<subclass>, which should work for you. – Jules Feb 1 '16 at 15:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.