3

I'm running into an issue where I have one generic that I'm trying to cast to another where the second's generic T parameter is a subclass of the one used in the first.

Here's my code, simplified for understanding...

public partial class HierarchicalItem
{
    public ObservableHierarchicalCollection<HierarchicalItem> ContainingCollection{ get; private set; }

    public HierarchicalItem Parent{ get{

        return (ContainingCollection != null)
            ? ContainingCollection.Owner
            : null;

    }}

}

public partial class HierarchicalItem
{

    public class ObservableHierarchicalCollection<T> : ObservableCollection<T>
    where T : HierarchicalItem
    {
        public ObservableHierarchicalCollection(HierarchicalItem owner)
        {
            this.Owner = owner;
        }

        public HierarchicalItem Owner{ get; private set; }

        private void ExistingMemberCheck(T item)
        {   
            if(item.ContainingCollection != null) throw new ExistingMemberException();
            item.ContainingCollection = this; // <-- This fails because of casting
        }

        protected override void InsertItem(int index, T item)
        {
            ExistingMemberCheck(item);
            base.InsertItem(index, item);
        }

        protected override void SetItem(int index, T item)
        {   
            CheckParent(item);

         // Get the item and unhook the hierarchy
            var existingItem = this[index];
            existingItem.ContainingCollection = null;

            base.SetItem(index, item);
        }

        protected override void RemoveItem(int index)
        {
         // Get the item and unhook the hierarchy
            var existingItem = this[index];
            existingItem.ContainingCollection = null;

            base.RemoveItem(index);

        }

    }

}

So how do I get around this?

Mark A. Donohoe
  • 28,442
  • 25
  • 137
  • 286
  • See [Covariance and Contravariance in Generics](http://msdn.microsoft.com/en-us/library/dd799517.aspx) – Magnus Jul 08 '11 at 15:10
  • possible duplicate of [c# Cast List to List](http://stackoverflow.com/questions/5115275/c-cast-listx-to-listy) – Magnus Jul 08 '11 at 15:16
  • @Magnus, not a duplicate. He's talking about members. I'm talking about the collection itself. Plus, I'm referring specifically to all generics that may show this, just using my code as an example. His is focused on lists and their contents. (IOW, I could've demonstrated this with non-collection-based classes.) – Mark A. Donohoe Jul 08 '11 at 15:50

3 Answers3

4

C# 4.0 supports explicit co-variance and contra-variance.

You could use the out keyword in the ObservableCollection interface declaration:

public interface ObservableCollection <out T> { 

   //The ObservableCollection  methods 
}

And then the interface will be co-variant.

more about it:

Covariance and Contravariance (C# and Visual Basic)

Covariance and Contravariance FAQ

How is Generic Covariance & Contra-variance Implemented in C# 4.0?

Community
  • 1
  • 1
Yochai Timmer
  • 48,127
  • 24
  • 147
  • 185
0

Switch the top part to something like this:

public partial class HierarchicalItem
    {
        public INotifyCollectionChanged ContainingCollection { get; private set; }

        public HierarchicalItem Parent
        {
            get
            {

                return (ContainingCollection != null)
                    ? ((ObservableHierarchicalCollection<HierarchicalItem>)ContainingCollection).Owner
                    : null;

            }
        }

    }

You have to use the non-generic interface because, as Nicholas, states co-variance is your enemy in this instance.

JaCraig
  • 1,053
  • 6
  • 10
0

In your example, the ObservableHierarchicalCollection<T> is only ever instantiated with T = HierarchicalItem. If that will always be the case, you could try making the collection non-generic and inheriting from ObservableCollection<HierarchicalItem> instead.

If not, you may be able to change HierarchicalItem to also be abstract and generic, (HierarchicalItem<T> where T : HierarchicalItem<T>) like IComparable and similar interfaces do. You would then have DerivedItem : HierarchicalItem<DerivedItem>, which would have a ContainingCollection of type ObservableHierarchicalCollection. Note that this could make mixing item types difficult.

Gideon Engelberth
  • 6,095
  • 1
  • 21
  • 22