views:

94

answers:

3

Here's a thinned out version of the classes I have.

public abstract class BaseParent { }

public abstract class ChildCollectionItem<T>
  where T : BaseParent
{
  // References a third-party object that acts as the parent to both the collection 
  // items and the collection itself.
  public T parent;

  // References the collection to which this item belongs. The owning collection
  // will share the same parent type. The second type argument indicates what
  // type of items the collection will store. 
  public ChildCollection<T, ChildCollectionItem<T>> owningCollection;
}

public abstract class ChildCollection<T, U> : CollectionBase
  where T : BaseParent
  where U : ChildCollectionItem<T>
{
  // References a third-party object that acts as the parent to both the collection 
  // items and the collection itself.
  public T parent;

  // Adds an item of type 'U' to the collection. When added, I want to set the 
  // owningCollection value of the item to 'this' collection.
  public int Add(U item)
  {
    int indexAdded = this.List.Add(item);

    // THIS LINE IS THROWING A COMPILE ERROR - Cannot convert type 
    // 'ChildCollection<T, U>' to 'ChildCollection<T, ChildCollectionItem<T>>'
    item.owningCollection = this;

    return indexAdded;
}

I realize this problem is probably due to the fact that the ChildCollection class isn't aware of the type constraints set in ChildCollectionItem, because if it was there shouldn't be a problem here. Type 'U' should always be a ChildCollectionItem where the ChildCollectionItem 'T' is always the same as the ChildCollection 'T'.

What I need to know is if there is a way I can cast 'this' so that it compiles, or modify my classes / constraints so the compiler can handle this without a cast.

+4  A: 

The problem is one of variance - U could be some type other than just ChildCollectionItem<T> - it could be a type derived from ChildCollectionItem<T>, and Foo<DerivedClass> isn't compatible with Foo<BaseClass>.

How about introducing another generic base class to ChildCollection<T, U>?

using System.Collections;

public abstract class BaseParent { }

public abstract class ChildCollectionItem<T>
  where T : BaseParent
{
  public T parent;

  public ChildCollection<T> owningCollection;
}

public abstract class ChildCollection<T> : CollectionBase
  where T : BaseParent
{
}

public abstract class ChildCollection<T, U> : ChildCollection<T>
  where T : BaseParent
  where U : ChildCollectionItem<T>
{
  public T parent;

  public int Add(U item)
  {
    int indexAdded = this.List.Add(item);
    item.owningCollection = this;
    return indexAdded;
  }
}
Jon Skeet
I didn't know you could have different types with the same name. Mind: blown.
recursive
Well, I feel like you are getting me closer to a solution here. But now that I've done that, the owningCollection member of ChildCollectionItem<T> can't access members of the derived class ChildCollection<T, U>, such as Add(), or other methods I had previously left out of the example - Contains(), Remove(), etc. And again, I get an error (now at runtime) when trying to convert owningCollection from ChildCollection<T> to ChildCollection<T, U>. I'm assuming this is for a similar reason that I was getting the other error.
Yes - you're probably trying to convert a `DerivedCollection<SomeType, DerivedCollectionItem>` to `ChildCollection<SomeType, ChildCollectionItem<SomeType>>` - and that variance just doesn't work. I suspect you'll either need to make `ChildCollectionItem` have two type parameters as well (so that you know `U`) or refactor your design more significantly.
Jon Skeet
@recursive: I suspect you already knew that. IComparable and IComparable<T> are two types with the same name in the same namespace but with different generic arities, for example.
Eric Lippert
@Jon/@Eric: I've used a similar pattern myself to solve problems where you want generic parameter variance but still be able to access constructed generic types from base classes. Sadly, this pattern doesn't have a name (that I know of) - which makes it a bit awkward to discuss. **Someone should really name it.**a hint hint, wink wink.
LBushkin
OK, then I hereby name that pattern "The Bushkin Manouevre".
Eric Lippert
A: 

Your constraint on U is very specific; U has to be of type ChildCollectionItem. Would it be possible in your application to define the collection as

public abstract class ChildCollection<T, ChildCollectionItem<T>>

That would eliminate the need to cast.

Cylon Cat
That's not a legal class declaration.
Eric Lippert
OK, I stand corrected....
Cylon Cat
+4  A: 

this problem is probably due to the fact that the ChildCollection class isn't aware of the type constraints set in ChildCollectionItem, because if it was there shouldn't be a problem here.

The statement above is incorrect.

Type 'U' should always be a ChildCollectionItem where the ChildCollectionItem 'T' is always the same as the ChildCollection 'T'.

How do you figure that? U is guaranteed via its constraint to be convertible via reference conversion to that type. It is not guaranteed to be that type, which is what is required. U could be any type derived from that type.

If U is always that type, then why is there a "U" in the first place? Why not just eliminate the type parameter entirely and replace every usage of it with the type you expect it to be?

Eric Lippert
I see what you're saying, but here's essentially what I'm trying to accomplish. I'm trying to create abstract classes for a collection and collection item pair that meet the following requirements.1) The collection object and item object must share the same parent type. 2) The collection is strongly typed for classes that inherit from it. 3) The abstract classes include code that keeps them in sync with one another. In the example above, when I add an item to a collection, a reference in the item class is set to point to that collection.Example to follow...
For example, I might want Comment and CommentCollection classes that belong to a ForumPost parent object.I'd like to be able to do:public Class ForumPost : BaseParent { }public class Comment : ChildCollectionItem<ForumPost>public class CommentCollection : ChildCollection<ForumPost, Comment>The collection would be strongly typed for Comment objects. The parent of both the Comment and CommentCollection objects would be ForumPost.