tags:

views:

359

answers:

4

I have the following class hierarchy:

public abstract class BaseData
{
  //some properties
}

public class CoData : BaseData
{
  //some properties
}

I am working with a method that requires the return type to be List<BaseData>. In the method, I have access to List<CoData>

public List<BaseData> Save()
{
  List<CoData> listCoData = GetData();
  return listCoData;
}

If I understand correctly, I can upcast from a CoData to a BaseData. But, when I have a list, it errors out even if I explicitly try to typecast.

Error:

Error   118 Cannot implicitly convert type 'System.Collections.Generic.List<CoData>' to System.Collections.Generic.List<BaseData>'

EDIT:

mquander's Conversion approach seems to work for me in 3.0

Is downcasting done the same way as well? from

ie., Can I do this - List<CoData> listCoData = listBaseData.Cast<BaseData>().ToList();

+7  A: 

Yes; welcome to variance. Ultimately, it isn't a list of BaseData - for example, if you had another subclass, a List<BaseData> would (at compile time) let you .Add it... but the runtime type wouldn't let you. The compiler is stopping you making a mistake.

In some scenarios, generics can help here... I discuss this at the end of this blog entry. Note that .NET 4.0 variance doesn't apply to lists.

Marc Gravell
+4  A: 

This is called "covariance" with respect to the parameter of your collection, and C# doesn't support this kind of covariance right now. In C# 4.0, user-defined types and some built-in types like IEnumerable<T> will support this.

In the meantime, you can work around it by creating a new enumeration with explicitly cast members (you might want to think about changing the return types of your stuff here to IEnumerable<T> so you don't have to actually cook up new collections all the time:)

public List<BaseData> Save()
{
    List<CoData> listCoData = GetData();
    return listCoData.Cast<BaseData>().ToList();
}

Depending on how GetData works, you might also consider a structure like this:

public List<T> Save<T>() where T : BaseData
{
    return listCoData = GetData<T>();
}

EDIT: Actually, as Marc and others undoubtedly will point out, List<T> can't be covariant like this because you can add members in ways that would break type safety if it were. However, if you sent back IEnumerable<T>s instead, you could use covariance here.

EDIT 2: In response to your additional question:

Can I do this? List<CoData> listCoData = listBaseData.Cast<BaseData>().ToList();

No. listBaseData.Cast<BaseData>().ToList(); returns an object of type List<BaseData> which can't be cast directly to a List<CoData>. That's why we had to go to this effort in the first place.

mquander
My 2nd question was different from my original question. The 2nd question was related to downcasting. I was curious to know if this approach can be used for going from base to derived as well as from derived to base
DotnetDude
+1  A: 

You'll have to wait until C# 4.0 to use generic covariance.

Anton Gogolev
No; .NET 4.0 variance does not apply here for at least 2 reasons; a list is neither "in" nor "out", and a list isn't an interface nor delegate-type. http://marcgravell.blogspot.com/2009/02/what-c-40-covariance-doesn-do.html
Marc Gravell
+1  A: 

Another variation on mquander's solution would be to implement Save as such:

public List<BaseData> Save()
{
    List<CoData> listCoData = GetData();
    return listCoData.ConvertAll<BaseData>(
        delegate(CoData c) { return c as BaseData; });
}

This would allow you to do some additional processing in the anonymous delegate, other than just casting it.

The important thing to remember here is that while CoData inherits from BaseData, List does not inherit from List.

epotter
Whats the diff b/w ConvertAll and Cast
DotnetDude
In the version I post, there is no difference because all the delegate does is cast the base type. If you needed to do some type checking or some special conversion, you could add it in the delegate.
epotter