泛型接口中的协变性

5

我想创建一个可排序的observableCollection,所以我开始创建一个继承observable的类,并添加了一些方法来对其进行排序,然后我希望该类能将索引保留到子项中,所以我创建了一个公开索引属性的接口,并将我的集合类T约束为我的接口类型,然后我希望能够从每个项中访问parentCollection,但由于父集合的类型是泛型,所以问题就出现了... 我尝试了很多解决方案,我认为协变或不变性可能是解决问题的方法,但我无法使其正常工作...

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ClassLibrary1
{
    public class SortableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>, ISortableCollection<T> where T : ISortable<T>
    {
        public void Sort()
        {
            //We all know how to sort something
            throw new NotImplementedException();
        }

        protected override void InsertItem(int index, T item)
        {
            item.Index = index;
            item.ParentCollection = this;
            base.InsertItem(index, item);
        }
    }

    public interface ISortableCollection<T> : IList<T>
    {
        void Sort();
    }

    public interface ISortable<T>
    {
        Int32 Index { get; set; }
        ISortableCollection<T> ParentCollection { get; set; }
    }

    public class BaseClass : ISortable<BaseClass>
    {
        public int Index { get; set; }

        public ISortableCollection<BaseClass> ParentCollection { get; set; }
    }

    public class DerivedClass : BaseClass { }

    public class Controller
    {
        SortableCollection<BaseClass> MyBaseSortableList = new SortableCollection<BaseClass>();
        SortableCollection<DerivedClass> MyDerivedSortableList = new SortableCollection<DerivedClass>();

        public Controller()
        {
            //do things
        }
    }
}

这是大致的设置。我想创建一个SortableCollection<DerivedClass>,但类型不匹配...应该如何正确处理?
确切的错误信息是:
“错误1 类型'ClassLibrary1.DerivedClass'不能用作泛型类型或方法'ClassLibrary1.SortableCollection<T>'中的类型参数'T'。没有从'ClassLibrary1.DerivedClass'到'ClassLibrary1.ISortable<ClassLibrary1.DerivedClass>'的隐式引用转换。c:\users\luigi.trabacchin\documents\visual studio 2013\Projects\ClassLibrary1\ClassLibrary1\Class1.cs 48 89 ClassLibrary1”
3个回答

9
问题在于你对 T 的限制是 "T 必须是一个 I<T>",而你已经传递了一个 DerivedClassT,但是 DerivedClass 不能转换成 I<DerivedClass>,它可以转换成 I<BaseClass>
我不知道你试图用 TI<T> 这个限制来表示什么。我知道人们经常使用这种模式来尝试表示 C# 类型系统实际上没有实现的限制。有关详细信息,请参阅我的文章。

http://blogs.msdn.com/b/ericlippert/archive/2011/02/03/curiouser-and-curiouser.aspx

我建议你大幅简化事情;你似乎在尝试在类型系统中捕获太多内容。
I不能转换为I的原因是,为了使变异起作用,必须将接口标记为支持变异; 标记T为out或in,具体取决于是否需要协变或逆变。
但是,由于IList是不变的,因此将派生接口变为协变或逆变是不合法的。考虑使用IEnumerable,因为它在T中是协变的。
为了使接口在T中是协变的,它只需要在输出位置使用T。List在输入和输出位置都使用T,因此无法协变或逆变。

我对这段代码进行了一些尝试,但由于ParentCollection既在in中又在out中,因此无法解决方差和IList基类被删除的问题。 - usr
嘿,感谢Eric提供的非常好的答案。我想要实现的是能够获取当前项的兄弟节点,切换它们的顺序,然后调用sort方法...我想我只需要检查父集合的类型并调用该方法,如果父集合实现了接口...使用约束条件我已经接近目标了,但感觉很奇怪,还是缺少一些东西。 - L.Trabacchin

2
您需要让DerivedClass成为ISortable<DerivedClass>
public class DerivedClass : BaseClass, ISortable<DerivedClass>
{
    public new ISortableCollection<DerivedClass> ParentCollection
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }
}

由于您从 IList<T> 派生,而该类型是不变的,因此无法在 T 上使用协变和逆变。

即使删除了 IList<T> 并移除了 getter,我目前也无法使其与方差协同工作。这并不是我的强项。如果可能的话,最好将此类型系统的部分留给它自己。

如果类型系统让您感到很困惑,请考虑动态解决方案:

((dynamic))item).ParentCollection = this;

顺便说一下,“为什么”很简单:如果您尝试将IList<TDerived>传递给接受IList<TBase>的某个东西,那么这将允许您在派生列表中调用.Add(Base item),这是不行的。 - Mark Sowul
当然,我尝试过在派生类中实现接口,但这会导致必须声明D.parent<D>,但这又会带来更多的问题。我想从observable派生,它从IList派生,所以...我想我会依靠类型检查而不是泛型。 - L.Trabacchin

0
为了感谢大家,我将发布我最终设计的内容。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ClassLibrary1
{
    public class SortableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>, ISortableCollection where T : ISortable, IComparable, IComparable<T>
    {
        public new void Add(T item)
        {
            if (this.Items.Contains(item))
                throw new InvalidOperationException("This list can contain the same item only once");
            base.Add(item);
        }

        public void Sort()
        {
            var sorted = this.Items.ToList();
            sorted.Sort();
            for (var i = 0; i < this.Items.Count; i++)
            {
                if (object.ReferenceEquals(this.Items[i], sorted[i]))
                {
                    this.Items[i].Index = i;
                    continue;
                }
                // if u want to support duplicates create a nextIndexOf and start searching from i
                var previousIndex = IndexOf(sorted[i]);
                Move(previousIndex, i);
            }
        }

        protected override void InsertItem(int index, T item)
        {
            item.Index = index;
            item.ParentCollection = this;
            base.InsertItem(index, item);
        }

        protected override void RemoveItem(int index)
        {
            this.Items[index].ParentCollection = null;
            base.RemoveItem(index);
        }

        protected override void ClearItems()
        {
            foreach (var item in this.Items)
                item.ParentCollection = null;
            base.ClearItems();
        }

        protected override void SetItem(int index, T item)
        {
            this.Items[index].ParentCollection = null;
            item.Index = index;
            item.ParentCollection = this;
            base.SetItem(index, item);
        }

        protected override void MoveItem(int oldIndex, int newIndex)
        {
            this.Items[oldIndex].Index = newIndex;
            this.Items[newIndex].Index = oldIndex;
            base.MoveItem(oldIndex, newIndex);
        }
    }

    public interface ISortableCollection : IList
    {
        void Sort();
    }

    public interface ISortable
    {
        Int32 Index { get; set; }
        ISortableCollection ParentCollection { get; set; }
    }

    public class BaseClass : ISortable, IComparable, IComparable<BaseClass>
    {
        public int Index { get; set; }

        public ISortableCollection ParentCollection { get; set; }

        public int CompareTo(object obj)
        {
            return CompareTo(obj as BaseClass);
        }

        public int CompareTo(BaseClass other)
        {
            if (other == null)
                return 1;
            return this.Index.CompareTo(other.Index);
        }
    }

    public class DerivedClass : BaseClass { }

    public class Controller
    {
        SortableCollection<BaseClass> MyBaseSortableList = new SortableCollection<BaseClass>();
        SortableCollection<DerivedClass> MyDerivedSortableList = new SortableCollection<DerivedClass>();

        public Controller()
        {
            //do things
            MyDerivedSortableList.Add(new DerivedClass());
            MyDerivedSortableList.Add(new DerivedClass());
            var derivedThing = new DerivedClass();
            MyDerivedSortableList.Add(derivedThing);
            var sibiling = derivedThing.ParentCollection[derivedThing.Index - 1] as BaseClass;  //way easier
            // switch the two objects order and call sort
            // calling a sort before the operation if indexes have been messed with
            // add an event to ISortable to notify the list the index has been changed and mark the list dirty
            derivedThing.Index -= 1;
            sibiling.Index += 1;
            derivedThing.ParentCollection.Sort();   // maybe the list was created where i couldn't access it
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接