In C#, can you cast one generic type to another wh

2020-07-22 17:11发布

问题:

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?

回答1:

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?



回答2:

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.



回答3:

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.