1

I have the following classes:

class Item { }
class MeetingItem : Item { }
class ItemGroup<T> { }

now, this works without an issue:

Item something;
something = new MeetingItem();

this however fails:

ItemGroup<Item> itemGroup;
itemGroup = new ItemGroup<MeetingItem>(); // Fails here

I'm getting the "Cannot Implicitly convert type 'ItemGroup<MeetingItem>' to 'ItemGroup<Item>'" error. Isn't that exactly what I'm doing above (assigning something to the type Item and then instantiating to MeetingItem)?

What I will have eventually is a collection of Items in the ItemGroup class along with a few other members. And then I'm going to have a collection of ItemGroups that will contain different types of derived Items. Ideally I'd like to have Item as an abstract class (maybe an interface, I might need to keep it a class depending on what I need to implement).

I'm open to any refactoring ideas.

Thanks.

Edit to add my solution without having a Generic ItemGroup: I decided to ditch generics... kinda... I cobbled together this:

public class ItemGroup {
  public Type ItemType => this.Items.GetType().GetGenericArguments()[0];
  public IList Items { get; set; }

  public ItemGroup(Type itemType) {
    var genericListType = typeof(List<>).MakeGenericType(itemType);
    Items = (IList)Activator.CreateInstance(genericListType);
  }
}

So, I can now do something like this:

List<ItemGroup> groups = new List<ItemGroup>();
groups.Add(new ItemGroup(typeof(MeetingItem));

and then I can test for the specific Item type by the following:

groups[0].ItemType == typeof(MeetingItem)

It seems a little hacky, but it works. I'm a little concerned about performance on the ItemType Property, and I'm open to any refactoring ideas.

Rob
  • 2,080
  • 4
  • 28
  • 48
  • 2
    You should take a look at covariance and contravariance: https://msdn.microsoft.com/en-us/library/ee207183.aspx – Ric Feb 05 '16 at 15:36
  • 1
    Explanation here: http://stackoverflow.com/questions/1842636/why-cannot-c-sharp-generics-derive-from-one-of-the-generic-type-parameters-like – joaoruimartins Feb 05 '16 at 15:37
  • Just because two types, `A` and `B`, have a particular inheritance relationship, that doesn't mean that `G` and `G` have the *same* inheritance relationship. – Damien_The_Unbeliever Feb 05 '16 at 15:42
  • Ok... Fair enough... I can't do that. I get it now. So, the question is, should I delete this question or leave it and have it redirect people to the duplicate? Or should I mark Luann's answer as the answer, or just leave it open? – Rob Feb 05 '16 at 15:55
  • I nominated to reopen this to put in my (hopefully) final answer. It involves using a non generic class (ArrayList) that I inherited to my own class. I think it'll work and might help others. I'd like to keep the reference to the duplicate however as that will help others as well. – Rob Feb 19 '16 at 14:08

1 Answers1

2

This is an issue with variance.

Consider a simple interface like this:

interface MyInterface<T>
{
  T GetStuff();
  void SetStuff(T value);
}

Now, you have MyInterface<A>, and MyInterface<B>, where B inherits from A.

It's always safe to return B instead of A. However, it isn't safe to return A instead of B. Therefore, looking at GetStuff, it should be possible to cast MyInterface<B> to MyInterface<A>, but not vice versa.

It's always safe to pass B instead of A. However, it isn't safe to pass A instead of B. Therefore, looking at SetStuff, it should be possible to cast MyInterface<A> to MyInterface<B>, but not vice versa.

The problem should be obvious - you can't fulfill both at once. There is no safe cast for both of the methods.

If you can avoid having both ways in a single interface, you can use the out/in keywords to specify which kind of variance is supported by your interface, but that's it. Looking at classes from the .NET framework:

IEnumerable<object> enumerable = Enumerable.Empty<string>(); // Safe, 
                                                             // enumerable is covariant
ICollection<object> collection = new Collection<string>();   // Error, 
                                                             // collection isn't covariant

Func<object> func = () => "Hi!"; // Safe, Func<T> is covariant
Action<object> func = (string val) => { ... }; // Error, Action<T> isn't covariant

On the other hand, with contravariance:

Func<string> func = () => new object(); // Error, Func<T> isn't contravariant
Action<string> func = (object val) => { ... }; // Safe, Action<T> is contravariant
Luaan
  • 62,244
  • 7
  • 97
  • 116