协方差和 IList

发布于 2024-11-03 18:33:59 字数 484 浏览 3 评论 0原文

我想要一个协变集合,其项目可以通过索引检索。 IEnumerable 是我所知道的唯一一个协变的 .net 集合,但它没有此索引支持。

具体来说,我想这样做:

List<Dog> dogs = new List<Dog>();

IEnumerable<Animal> animals = dogs;
IList<Animal> animalList = dogs; // This line does not compile

现在,我知道为什么这是一个问题。 List 实现了具有 Add 方法的 ICollection。通过向上转换为 Animals 的 IList,它将允许后续代码添加“真实”List 集合中不允许的任何类型的动物。

那么有人知道支持索引查找并且也是协变的集合吗?我不想创建自己的。

I would like a Covariant collection whose items can be retrieved by index. IEnumerable is the only .net collection that I'm aware of that is Covariant, but it does not have this index support.

Specifically, I'd like to do this:

List<Dog> dogs = new List<Dog>();

IEnumerable<Animal> animals = dogs;
IList<Animal> animalList = dogs; // This line does not compile

Now, I'm aware of why this is a problem. List implements ICollection that has an Add method. By up casting to IList of Animals, it would allow subsequent code to add any type of animal which is not allowed in the "real" List<Dog> collection.

So is anyone aware of a collection that supports index lookups that is also covariant? I would like to not create my own.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

最好是你 2024-11-10 18:33:59

更新:从 .NET 4.5 开始,有 IReadOnlyListIReadOnlyCollection 两者都是协变的;后者基本上是 IEnumerable 加上 Count;前者添加了T this[int index] {get;}。还应该注意的是,IEnumerable 从 .NET 4.0 开始是协变的。

ListReadOnlyCollection(通过 List.AsReadOnly())实现这两个。


它只能是协变的,如果它只有一个 get 索引器,即

public T this[int index] { get; }

但所有主集合都有 {get;set;},这使得这很尴尬。我不知道有什么足够的,但你可以包装它,即编写一个扩展方法:

var covariant = list.AsCovariant();

它是一个IList的包装器,仅公开 IEnumerableget 索引器...?应该只需要几分钟的工作...

public static class Covariance
{
    public static IIndexedEnumerable<T> AsCovariant<T>(this IList<T> tail)
    {
        return new CovariantList<T>(tail);
    }
    private class CovariantList<T> : IIndexedEnumerable<T>
    {
        private readonly IList<T> tail;
        public CovariantList(IList<T> tail)
        {
            this.tail = tail;
        }
        public T this[int index] { get { return tail[index]; } }
        public IEnumerator<T> GetEnumerator() { return tail.GetEnumerator();}
        IEnumerator IEnumerable.GetEnumerator() { return tail.GetEnumerator(); }
        public int Count { get { return tail.Count; } }
    }
}
public interface IIndexedEnumerable<out T> : IEnumerable<T>
{
    T this[int index] { get; }
    int Count { get; }
}

Update: from .NET 4.5 onwards there is IReadOnlyList<out T> and IReadOnlyCollection<out T> which are both covariant; The latter is basically IEnumerable<out T> plus Count; the former adds T this[int index] {get;}. It should also be noted that IEnumerable<out T> is covariant from .NET 4.0 onwards.

Both List<T> and ReadOnlyCollection<T> (via List<T>.AsReadOnly()) implement both of these.


It can only be covariant if it only has a get indexer, i.e.

public T this[int index] { get; }

But all main collections have {get;set;}, which makes that awkward. I'm not aware of any that would suffice there, but you could wrap it, i.e. write an extension method:

var covariant = list.AsCovariant();

which is a wrapper around an IList<T> that only exposes the IEnumerable<T> and the get indexer...? should be only a few minutes work...

public static class Covariance
{
    public static IIndexedEnumerable<T> AsCovariant<T>(this IList<T> tail)
    {
        return new CovariantList<T>(tail);
    }
    private class CovariantList<T> : IIndexedEnumerable<T>
    {
        private readonly IList<T> tail;
        public CovariantList(IList<T> tail)
        {
            this.tail = tail;
        }
        public T this[int index] { get { return tail[index]; } }
        public IEnumerator<T> GetEnumerator() { return tail.GetEnumerator();}
        IEnumerator IEnumerable.GetEnumerator() { return tail.GetEnumerator(); }
        public int Count { get { return tail.Count; } }
    }
}
public interface IIndexedEnumerable<out T> : IEnumerable<T>
{
    T this[int index] { get; }
    int Count { get; }
}
懒猫 2024-11-10 18:33:59

下面是我为解决这种情况而编写的一个类:

public class CovariantIListAdapter<TBase, TDerived> : IList<TBase>
    where TDerived : TBase
{
    private IList<TDerived> source;

    public CovariantIListAdapter(IList<TDerived> source)
    {
        this.source = source;
    }

    public IEnumerator<TBase> GetEnumerator()
    {
        foreach (var item in source)
            yield return item;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public void Add(TBase item)
    {
        source.Add((TDerived) item);
    }

    public void Clear()
    {
        source.Clear();
    }

    public bool Contains(TBase item)
    {
        return source.Contains((TDerived) item);
    }

    public void CopyTo(TBase[] array, int arrayIndex)
    {
        foreach (var item in source)
            array[arrayIndex++] = item;
    }

    public bool Remove(TBase item)
    {
        return source.Remove((TDerived) item);
    }

    public int Count
    {
        get { return source.Count; }
    }

    public bool IsReadOnly
    {
        get { return source.IsReadOnly; }
    }

    public int IndexOf(TBase item)
    {
        return source.IndexOf((TDerived) item);
    }

    public void Insert(int index, TBase item)
    {
        source.Insert(index, (TDerived) item);
    }

    public void RemoveAt(int index)
    {
        source.RemoveAt(index);
    }

    public TBase this[int index]
    {
        get { return source[index]; }
        set { source[index] = (TDerived) value; }
    }
}

现在您可以编写如下代码:

List<Dog> dogs = new List<Dog>();
dogs.Add(new Dog { Name = "Spot", MaximumBarkDecibals = 110 });

IEnumerable<Animal> animals = dogs;
IList<Animal> animalList = new CovariantIListAdapter<Animal, Dog>(dogs);

animalList.Add(new Dog { Name = "Fluffy", MaximumBarkDecibals = 120 });

更改在两个列表中都可见,因为实际上仍然只有 1 个列表。适配器类只是传递调用,根据需要转换项目以实现所需的 IList 接口。

显然,如果你向 animalList 添加除 Dogs 之外的任何内容,它都会抛出异常,但这满足了我的需求。

Here's a class I wrote to address this scenario:

public class CovariantIListAdapter<TBase, TDerived> : IList<TBase>
    where TDerived : TBase
{
    private IList<TDerived> source;

    public CovariantIListAdapter(IList<TDerived> source)
    {
        this.source = source;
    }

    public IEnumerator<TBase> GetEnumerator()
    {
        foreach (var item in source)
            yield return item;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public void Add(TBase item)
    {
        source.Add((TDerived) item);
    }

    public void Clear()
    {
        source.Clear();
    }

    public bool Contains(TBase item)
    {
        return source.Contains((TDerived) item);
    }

    public void CopyTo(TBase[] array, int arrayIndex)
    {
        foreach (var item in source)
            array[arrayIndex++] = item;
    }

    public bool Remove(TBase item)
    {
        return source.Remove((TDerived) item);
    }

    public int Count
    {
        get { return source.Count; }
    }

    public bool IsReadOnly
    {
        get { return source.IsReadOnly; }
    }

    public int IndexOf(TBase item)
    {
        return source.IndexOf((TDerived) item);
    }

    public void Insert(int index, TBase item)
    {
        source.Insert(index, (TDerived) item);
    }

    public void RemoveAt(int index)
    {
        source.RemoveAt(index);
    }

    public TBase this[int index]
    {
        get { return source[index]; }
        set { source[index] = (TDerived) value; }
    }
}

Now you can write code like this:

List<Dog> dogs = new List<Dog>();
dogs.Add(new Dog { Name = "Spot", MaximumBarkDecibals = 110 });

IEnumerable<Animal> animals = dogs;
IList<Animal> animalList = new CovariantIListAdapter<Animal, Dog>(dogs);

animalList.Add(new Dog { Name = "Fluffy", MaximumBarkDecibals = 120 });

The changes are visible in both lists, because there's really still only 1 list. The adapter class just passes the calls through, casting items as necessary to achieve the desired IList<TBase> interface.

Obviously, if you add anything but Dogs to animalList, it will throw an exception, but this met my needs.

情归归情 2024-11-10 18:33:59

从技术上讲,有数组集合。它的差异有点被破坏,但它满足了你的要求。

IList<Animal> animals;
List<Dog> dogs = new List<Dog>();
animals = dogs.ToArray();

当然,如果您尝试将 Tiger 放入数组中的任何位置,那么在运行时您会发生相当惊人的爆炸。

Technically, there's the array collection. It's sort of broken in its variance, but it does what you ask.

IList<Animal> animals;
List<Dog> dogs = new List<Dog>();
animals = dogs.ToArray();

You will, of course, blow up rather spectacularly at runtime if you try to put a Tiger in the array anywhere.

梦断已成空 2024-11-10 18:33:59

从 .NET Framework 4.5 开始,存在一个协变接口 IReadOnlyList。它本质上与 Mark Gravell 的答案中的 IIndexedEnumerable 接口相同。

IReadOnlyList 的实现如下:

  /// <summary>
  /// Represents a read-only collection of elements that can be accessed by index.
  /// </summary>
  /// <typeparam name="T">The type of elements in the read-only list. This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics.</typeparam>
    public interface IReadOnlyList<out T> : IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable
      {
        /// <summary>
        /// Gets the element at the specified index in the read-only list.
        /// </summary>
        /// 
        /// <returns>
        /// The element at the specified index in the read-only list.
        /// </returns>
        /// <param name="index">The zero-based index of the element to get. </param>
        T this[int index] { get; }
      }

As of .NET Framework 4.5, there exists an interface IReadOnlyList which is covariant. It is essentially the same as the IIndexedEnumerable interface in Mark Gravell's answer.

IReadOnlyList is implemented like this:

  /// <summary>
  /// Represents a read-only collection of elements that can be accessed by index.
  /// </summary>
  /// <typeparam name="T">The type of elements in the read-only list. This type parameter is covariant. That is, you can use either the type you specified or any type that is more derived. For more information about covariance and contravariance, see Covariance and Contravariance in Generics.</typeparam>
    public interface IReadOnlyList<out T> : IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable
      {
        /// <summary>
        /// Gets the element at the specified index in the read-only list.
        /// </summary>
        /// 
        /// <returns>
        /// The element at the specified index in the read-only list.
        /// </returns>
        /// <param name="index">The zero-based index of the element to get. </param>
        T this[int index] { get; }
      }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文