views:

35

answers:

1

I have some entities created with LINQ-to-SQL. Six of these entities (representing values primarily in drop-down lists) implement an interface I've called IValue. I did this because the UI layer is going to have to account for a couple special cases -- notably, what to display if the original value on a record has been flagged as deleted.

The repository has a variety of ListAllXXX methods for these guys. All of these return generic lists typed to the appropriate entity type. An example:

public static List<ContactType> ListAllContactTypes(DeletedOptions getDeleted)
{ /* requisite code */ }

ContactType does implement IValue, of course.

There's another set of services designed to actually retrieve the UI-specific list. The basic pattern is thus:

// One of these for each entity type
public static List<IValue> GetContactTypeList(ContactType target)
{
    List<IValue> ret = LovRepository.ListAllContactTypes(DeletedOptions.NoDeleted);
    PrepList(ret, target);

    return ret;
}

// All of the above methods use this guy
private static void PrepList(List<IValue> list, IValue targetEntity)
{
    list.Insert(0, new DummyValue() { Description = "Add New ... ", ID = 0 });

    if (targetEntity != null && !(list.Contains(targetEntity))
        list.Add(new DummyValue() { Description = "[deleted]", ID = -1 });
}

(I should probably note that DummyValue is a simple class I created that also implements IValue, and whose whole purpose in life is to serve as the "add new" and "deleted" menu options.)

All of this comes about because I didn't want to write a few dozen lines of almost identical code -- the whole reason I thought we had covariance.

The code as written here does not compile. I've tried a manual cast to List<IValue> on the ListAllContactTypes line; that compiles, but fails at run-time with an invalid cast exception.

How can I get where I want to go here? Is there a limitation on using generic variance with interfaces? If so, is there an easy way around it? If not, am I relegated to writing a bunch of highly-repetitive-but-just-slightly-different code? (Which I'm really trying to avoid.)

This may well be a duplicate, but my Google-fu is failing me right now. If it is, please vote to close accordingly. (I'll pile on a close vote if that's the case!)

+1  A: 

Co- and contravariance can only be used on delegate and interface declarations, so your code won't work. But you can cast your result using the Cast extension method, and make your code work:

List<IValue> ret = LovRepository.ListAllContactTypes(DeletedOptions.NoDeleted)
    .Cast<IValue>().ToList();

This casts each element in the list and creates a new list of IValue elements.

The reason why you need this, has to do with type safety. In your example code of course, there is no problem. But the method ListAllContactTypes are declared to return type List<ContactType>. If you could just assign it to List<IValue>, you can put any IValue in - so if some other code expects the list to contain ContactType explicitly, that code will break. Consider this example:

List<ContactType> listOfContactType =  // Some method ....
List<IValue> list = listOfContactType; // This line not allowed.

If that compiled, you would be able to do:

list.Insert(0,new DummyType());

But you still have to consider the original reference, on which it should be allowed to do:

ContactType contact = listOfContactType[0];  // Woops, element is not ContactType.

You can't do this, because the element in the list is DummyType. Thankfully the compiler saved us early.

driis
You win 1 internet. Spend it wisely! (And many thanks, I was going nuts on that.) (And yes, it worked like a champ!)
John Rudy