AndrewJacksonZA
AndrewJacksonZA

Reputation: 1273

C# Casting a List<ObjBase> as List<Obj>

Why can I not cast a List<ObjBase> as List<Obj>? Why does the following not work:

internal class ObjBase
   {
   }

internal class Obj : ObjBase
   {
   }   

internal class ObjManager
{
    internal List<Obj> returnStuff()
    {
       return getSomeStuff() as List<Obj>;
    }

    private List<ObjBase> getSomeStuff()
    {
       return new List<ObjBase>();
    }

}

Instead I have to do this:

internal class ObjBase
   {
   }

internal class Obj : ObjBase
   {
   }

internal class ObjManager
{
    internal List<Obj> returnStuff()
    {
       List<ObjBase> returnedList = getSomeStuff();
       List<Obj> listToReturn = new List<Obj>(returnedList.Count);
       foreach (ObjBase currentBaseObject in returnedList)
       {
          listToReturn.Add(currentBaseObject as Obj);
       }
       return listToReturn;
    }

    private List<ObjBase> getSomeStuff()
    {
       return new List<ObjBase>();
    }
}

I get the following error in Visual Studio 2008 (shortened for readability):

Cannot convert type 'List' to 'List' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion

Thanks.

Upvotes: 30

Views: 67611

Answers (9)

Bigjim
Bigjim

Reputation: 2255

list.ConvertAll looks tempting but has 1 big disadvantage: it will create a whole new list. This will impact performance and memory usage especially for big lists.

With a bit more effort you can create a wrapper list class that keeps the original list as an internal reference, and convert the items only when they are used.

Usage:

var x = new List<ObjBase>();
var y = x.CastList<ObjBase, Obj>(); // y is now an IList<Obj>

Code to add to your library:

public static class Extensions
{
    public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
    {
        return new CastedList<TTo, TFrom>(list);
    }
}

public class CastedList<TTo, TFrom> : IList<TTo>
{
    public IList<TFrom> BaseList;

    public CastedList(IList<TFrom> baseList)
    {
        BaseList = baseList;
    }

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

    // IEnumerable<>
    public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }

    // ICollection
    public int Count { get { return BaseList.Count; } }
    public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
    public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
    public void Clear() { BaseList.Clear(); }
    public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
    public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
    public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }

    // IList
    public TTo this[int index]
    {
        get { return (TTo)(object)BaseList[index]; }
        set { BaseList[index] = (TFrom)(object)value; }
    }

    public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
    public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
    public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}

public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
    public IEnumerator<TFrom> BaseEnumerator;

    public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
    {
        BaseEnumerator = baseEnumerator;
    }

    // IDisposable
    public void Dispose() { BaseEnumerator.Dispose(); }

    // IEnumerator
    object IEnumerator.Current { get { return BaseEnumerator.Current; } }
    public bool MoveNext() { return BaseEnumerator.MoveNext(); }
    public void Reset() { BaseEnumerator.Reset(); }

    // IEnumerator<>
    public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}

Upvotes: 3

Andrew Marais
Andrew Marais

Reputation: 965

Here is how I fixed the Conversion from a

list<SomeOtherObject>

to a

object

and then to a

List<object>

https://stackoverflow.com/a/16147909/2307326

Upvotes: 0

Joachim Sauer
Joachim Sauer

Reputation: 308021

I can only describe the "problem" from a Java view, but from what little I know this aspect is the same in both C# and Java:

A List<ObjBase> is not a List<Obj>, because it could contain an ObjBase object which is not a Obj object.

The other way around a List<Obj> can not be cast to a List<ObjBase> because the former guarantees to accept an Add() call with a ObjBase argument, which the latter will not accept!

So to summarize: even though a Obj is-a ObjBase a List<Obj> is not a List<ObjBase>.

Upvotes: 10

AndrewJacksonZA
AndrewJacksonZA

Reputation: 1273

Lazarus:
I thought that the compiler would realise that I wanted actions done on the objects of the list and not that I was trying to cast the list itself.

Some more information:

public abstract class ObjBase
   {
   }

internal interface IDatabaseObject
   {
   }

public class Obj : ObjBase, IDatabaseObject
   {
   }


internal interface IDatabaseObjectManager
   {
      List<ObjBase> getSomeStuff();
   }

public class ObjManager : IObjManager
{
    public List<Obj> returnStuff()
    {
       return getSomeStuff().Cast <Customer>().ToList<Customer>();
    }

    private List<ObjBase> getSomeStuff()
    {
       return new List<ObjBase>();
    }
}

Now client code outside of this DLL can go: ObjManager objM = new ObjManager(); List listOB = objM.returnStuff(); I'm going to be creating several Obj and ObjManager types for this part (O/RM) of the application.

(Darn comment block ran out of characters! :-)

Upvotes: 0

Grzenio
Grzenio

Reputation: 36649

This is a major pain in C# - this is how generics were designed. List doesn't extend List, its just a completely different type. You can't cast or assign them to each other in any way, your only option is to copy one list to the other one.

Upvotes: 0

RaYell
RaYell

Reputation: 70414

You can use Cast and ToList extension methods from System.Linq to have this in one line.

Instead of

internal List<Obj> returnStuff()
{
   return getSomeStuff() as List<Obj>;
}

do this:

internal List<Obj> returnStuff()
{
   return getSomeStuff().Cast<Obj>().ToList();
}

Upvotes: 41

Scott Ivey
Scott Ivey

Reputation: 41558

C# currently does not support variance for generic types. From what I've read, this will change in 4.0.

See here for more information on variance in generics.

Upvotes: 1

Jimmeh
Jimmeh

Reputation: 2862

Linq has a ConvertAll method. so something like

list.ConvertAll<Obj>(objBase => objbase.ConvertTo(obj));

I'm not sure what else to suggest. I assume ObjBase is the base class, and if all ObjBase objects are Obj objects, i'm not sure why you would have the two objects in the first place. Perhaps i'm off the mark.

Edit: the list.Cast method would work better than the above, assuming they are castable to each other. Forgot about that until I read the other answers.

Upvotes: 0

Lazarus
Lazarus

Reputation: 43074

I think you are misunderstanding the cast you are trying to do. You are thinking that you are changing the type of the object that is stored in the list, where you are actually trying to change the type of the list itself. It rather makes sense that you can't change the list itself as you have already populated it.

You might look at it as a list of a base class and then cast it when you are processing the list items, that would be my approach.

What is the purpose of this attempted cast?

Upvotes: 1

Related Questions