Using covariance with base interface type in .NET 4?
I have some objects created with LINQ-to-SQL. Six of these objects (representing values mainly in dropdowns) implement the interface I named IValue
. I did this because the UI layer has to deal with a few special cases - especially what to display if the original value in the record was marked as deleted.
There are many methods ListAllXXX
in the repository for these guys. They all return generic lists typed for the respective entity type. Example:
public static List<ContactType> ListAllContactTypes(DeletedOptions getDeleted)
{ /* requisite code */ }
ContactType
implements IValue
, of course.
There is another set of services for actually getting the UI specific list. So the main template is:
// 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
- this is a simple class I created that implements as well IValue
, and its whole purpose in life is to use the "add new" and "removed" options.)
This all happens 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 written here does not compile. I tried manual casting to List<IValue>
inline ListAllContactTypes
; which compiles but fails at runtime with an invalid cast exception.
How can I get where I want to go? Is there a limitation on using shared variance with interfaces? If so, is there an easy way? If not, am I relegated to writing a bunch of very repetitive but just slightly different code? (Which I really try to avoid.)
It might be a duplicate, but my google-fu doesn't give me the right right now. If so, please vote to close accordingly. (If that happens, I'm going to vote honestly!)
a source to share
Co- and contravariance can only be used for delegate and interface declarations, so your code won't work. But you can cast your result with Cast extension method and do your code:
List<IValue> ret = LovRepository.ListAllContactTypes(DeletedOptions.NoDeleted)
.Cast<IValue>().ToList();
This drops every item in the list and creates a new IValue list of items.
The reason you need this is due to type safety. Of course, there is no problem in your example code. But the ListAllContactTypes method is declared as the return type of List <ContactType>. If you can just assign it to a List <IValue>, you can put any IValue in, so if any other code expects the list to contain the ContactType explicitly, that code will break. Consider this example:
List<ContactType> listOfContactType = // Some method ....
List<IValue> list = listOfContactType; // This line not allowed.
If it's compiled, you should be able to:
list.Insert(0,new DummyType());
But you should still consider the original link where this should be allowed:
ContactType contact = listOfContactType[0]; // Woops, element is not ContactType.
You cannot do this because the item is in the DummyType list. Fortunately, the compiler saved us sooner.
a source to share