Question

I wanted to make a read-only indexable "view" of a list, with the slight twist that the read only view should enumerate to an interface of the list type.

interface IIndexable<out T> : IEnumerable<T>
{
   T this[int i] { get; }
   int Count { get; }
}

The usage scenario would look something like this.

List<Dog> dogs = new List<Dog>[]{ dog1, dog2 }; // Dog implements IAnimal

IIndexable<IAnimal> animals = dogs.AsIIndexable<Dog, IAnimal>();
IAnimal first = animals[0];

I thought this would be possible given covariance and type constraints, but maybe I'm mistaken here. My first attempt looks like this, which doesn't work because of the type of the enumerator.

internal class ListIndexable<TC, T> : IIndexable<T> where TC : T
{
   private List<TC> list;
   public ListIndexable(List<TC> list) { this.list = list; }

   public T this[int i] { get { return list[i]; } }

   public IEnumerator<T> GetEnumerator()
   {
      return list.GetEnumerator(); // Computer says no.
   }
}

Why can't I return an IEnumerator<TC> as an IEnumerator<T>? The type is covariant and I have constrained it with TC : T. Would it really be unsafe to return an IEnumerator<TC>, or is it simply a weakness here that the compiler's reasoning ignores the type constraint?

I could solve it by using a custom enumerator that wraps the IEnumerator<TC> and returns its items as T's which is of course perfectly legal, but I'm just curious why the above solution can't work.

internal class ListIndexable<TC, T> : IIndexable<T> where TC : T
{
   private List<TC> list;
   public ListIndexable(List<TC> list) { this.list = list; }

   public T this[int i] { get { return list[i]; } }

   public IEnumerator<T> GetEnumerator()
   {
      return new Enumerator(list.GetEnumerator()); 
   }

   class Enumerator : IEnumerator<T>
   {
      private IEnumerator<TC> inner;
      internal Enumerator(IEnumerator<TC> inner) { this.inner = inner; }

      public bool MoveNext()
      {
         return inner.MoveNext();
      }

      public T Current
      {
         get { return inner.Current; }
      }

      // ...
   }
}
Was it helpful?

Solution

Your original code does not work because of value types. You cannot do the following:

IEnumerable<int> ints = new[] { 1, 2 };
IEnumerable<object> os = ints;

so the compiler won't accept your definition since it can't guarantee that IEnumerable<TC> is compatible with IEnumerable<T> in general. If you add class constraints to T and TC it compiles:

internal class ListIndexable<TC, T> : IIndexable<T> where TC : class, T where T : class
{
}

However, you can define ListIndexable with only one type parameter instead of two. If you change your definition to:

internal class ListIndexable<T> : IIndexable<T>
{
   private List<T> list;
   public ListIndexable(List<T> list) { this.list = list; }

   public T this[int i] { get { return list[i]; } }

   public IEnumerator<T> GetEnumerator()
   {
      return list.GetEnumerator();
   }
}

then you can do:

IIndexable<IAnimal> animals = dogs.AsIIndexable();

where AsIndexable can be defined simply as:

public static ListIndexable<T> AsIndexable<T>(this List<T> list)
{
    return new ListIndexable<T>(list);
}

OTHER TIPS

If all you need is to create a read-only collection, you can use ReadOnlyCollection to wrap your list, eg:

var dogs = new List<Dog> {new Dog{Name="A"}, new Dog{Name="B"}, new Dog{Name="C"}};
var ro=new ReadOnlyCollection<Dog>(dogs);

IEnumerable<IAnimal> a = ro;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top