views:

311

answers:

5

I have a DataSource in my control which is always a List<T> where T has to inherit from IEntity.

public class MyClass<T> where T : IEntity
{
    public List<T> DataSource
    {
        get;
        set;
    }
}

Now, obviously you can't cast a List<T> to a List<IEntity> doing the following:

List<IEntity> wontWork = (List<IEntity>)this.DataSource;

How can I get the DataSource as a List of IEntity, whilst still being able to add and remove items from the DataSource? I.e. I could do the following, but removing from the List it returns would not remove from the DataSource:

public List<TOut> GetDataSourceCopyAsUnderlyingType<TOut>()
{

    if (this.DataSource == null)
    {
        return new List<TOut>();
    }
    else
    {

        // Get the list and the enumerator
        IList list = (IList)this.DataSource;
        IEnumerator enumerator = list.GetEnumerator();

        // Build the target list
        List<TOut> targetList = new List<TOut>();

        int i = 0;
        while (enumerator.MoveNext())
        {
         TOut entity = (TOut)list[i];
         targetList.Add(entity);
         i++;
        }

        return targetList;

        }

    }

Basically, I need some way of doing the following:

List<IEntity> interfaceList = this.GetDataSourceAsAnotherType<IEntity>();
int dataSourceCount = this.DataSource.Count;   // Equals 5
int interfaceCount = interfaceList.Count;      // Equals 5

interfaceList.RemoveAt(0);
int dataSourceCount = this.DataSource.Count;   // Equals 4
int interfaceCount = interfaceList.Count;      // Equals 4

And just to add, I don't mind if it means I've got to use a different type instead of a List.

EDIT: Sorry, forgot to say I'm using .Net2.0 and cannot move to .Net 3.5.

A: 

ok this might be completely beside the point but, how about using a little bit of Linq?

var interfaceList = objectList.ConvertAll<Interface>(o => (Interface)o);

this way you can cast the objectList easily. hope this helps to find the solution...

Roel
Sorry, I'm using .Net2.0, I forgot to specify.
GenericTypeTea
@Roel, @GenericTypeTea: `ConvertAll` is nothing to do with LINQ, it's a method on List<T> itself and is available in .NET2 (although without C#3 you'd need to use "old" delegate syntax rather than a lambda). However `ConvertAll` doesn't meet the requirements anyway because it returns a new list rather than doing an in-place conversion on the existing list.
LukeH
Thanks for explaining Luke. I'm aware that ConvertAll exists in List<T>, however I've tried that already and know that it doesn't meet my requirements as you've pointed out.
GenericTypeTea
A: 

I'm in favor of linq too, but you can do it like:

var interfaceList = objectList.Cast<IEntity>();

Which is shorter and more expressive.

klausbyskov
I'm using .Net2.0. I can't therefor use linq.
GenericTypeTea
@klausbyskov: This doesn't meet the requirements anyway because it returns a new list, rather than doing an in-place conversion on the existing list.
LukeH
Hmmm, ok... then hmmm... Can you cast it to an IEnumerable<IEntity> ? I have some code doing exactly that, and that works... but I'm in .net 4 country here so I don't know if there's something new and shining going on that I'm not aware of.
klausbyskov
@Luke, you're right.
klausbyskov
+5  A: 

It would be a monumentally bad idea if this were allowed, which is why it isn't. I can add any old IEntity to a List<IEntity> which will blow up if that IEntity can't be cast to T. Whilst all Ts are IEntities, not all IEntities are Ts.

This works with arrays because arrays have a deliberate subtyping hole (as they do in Java). Collections do not have a subtyping hole.

DrPizza
Ah of course. Makes perfect sense now as to why you can't do it. Thanks for that DrPizza.
GenericTypeTea
Any alternative suggestions as to what I could do instead?
GenericTypeTea
Copy out to a List<IEntity>, copy back in afterwards. If you don't need growability (just replacement of elements) than an array will work.
DrPizza
+2  A: 

Create a wrapper class that seamlessly converts. Untested sample:

public class CastList<TTarget, TOriginal> 
  : IList<TTarget> where TOriginal : TTarget
{
  List<TOriginal> _orig;
  public CastList(List<TOriginal> orig) { _orig = orig; }

  public Add(TTarget item) { _orig.Add(item); }

  public TTarget this[int i] 
  {
    get { return (TTarget)_orig[i]; }
    set { _orig[i] = value; }
  }

  public IEnumerator<TTarget> GetEnumerator() 
  {
     foreach(TOriginal item in _orig)
       yield return (TTarget)item;
  }

  // etc...
}

Manipulations of the original list will also be reflected in the wrapper. To use this, just construct it with your DataSource.

gWiz
+2  A: 

What DrPizza said, but with more code:

public class ListFacade<TIn, TOut> : IList<TOut> where TIn : TOut
{
    private readonly IList<TIn> innerList;

    public ListFacade(IList<TIn> innerList)
    {
        this.innerList = innerList;
    }

    public int Count
    {
        get { return this.innerList.Count; }
    }

    public bool IsReadOnly
    {
        get { return this.innerList.IsReadOnly; }
    }

    public TOut this[int index]
    {
        get { return this.innerList[index]; }
        set { this.innerList[index] = (TIn)value; }
    }

    public void Add(TOut item)
    {
        this.innerList.Add((TIn)item);
    }

    public void Clear()
    {
        this.innerList.Clear();
    }

    public bool Contains(TOut item)
    {
        return (item is TIn) && this.innerList.Contains((TIn)item);
    }

    public void CopyTo(TOut[] array, int arrayIndex)
    {
        var inArray = new TIn[this.innerList.Count];
        this.innerList.CopyTo(inArray, arrayIndex);
        Array.Copy(inArray, array, inArray.Length);
    }

    public IEnumerator<TOut> GetEnumerator()
    {
        foreach (var item in this.innerList)
        {
            yield return item;
        }
    }

    System.Collections.IEnumerator
        System.Collections.IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }

    public int IndexOf(TOut item)
    {
        return (item is TIn) ? this.innerList.IndexOf((TIn)item) : -1;
    }

    public void Insert(int index, TOut item)
    {
        this.innerList.Insert(index, (TIn)item);
    }

    public bool Remove(TOut item)
    {
        return (item is TIn) && this.innerList.Remove((TIn)item);
    }

    public void RemoveAt(int index)
    {
        this.innerList.RemoveAt(index);
    }

Add, Insert and the indexer set will blow up if the argument is not of type TIn.

dtb
Whilst not as bad as the array subtyping hole, I'm still not sure this is really something that should be encouraged.
DrPizza
Depends on the use case, I'd say. I wouldn't recommend designing a framework this way, but if it's just an implementation detail to glue some classes together, why not?
dtb
Well the real question I would have is, why isn't DataSource just a List<IEntity> in the first place? If he wants to treat them as plain ol' IEntities anyway (and not use any T-specific methods), it would seem easier to just store them in a List<IEntity> in the first place.
DrPizza