Why can't I assign a ListDerived to a ListBase?
The simplest way to understand why this is not allowed is the following example:
abstract class Fruit
{
}
class Apple : Fruit
{
}
class Banana : Fruit
{
}
// This should intuitively compile right? Cause an Apple is Fruit.
List<Fruit> fruits = new List<Apple>();
// But what if I do this? Adding a Banana to a list of Apples
fruits.Add(new Banana());
The last statement would ruin the type safety of .NET.
Arrays however, do allow this:
Fruit[] fruits = new Apple[10]; // This is perfectly fine
However, putting a Banana
into fruits
would still break type safety, so therefor .NET has to do a type check on every array insertion and throw an exception if it's not actually an Apple
. This is potentially a (small) performance hit, but this can be circumvented by creating a struct
wrapper around either type as this check does not happen for value types (because they can't inherit from anything). At first, I didn't understand why this decision was made, but you'll encounter quite often why this can be useful. Most common is String.Format
, which takes params object[]
and any array can be passed into this.
In .NET 4 though, there's type safe covariance/contravariance, which allows you to make some assignments like these, but only if they're provably safe. What's provably safe?
IEnumerable<Fruit> fruits = new List<Apple>();
The above works in .NET 4, because IEnumerable<T>
became IEnumerable<out T>
. The out
means that T
can only ever come out of fruits
and that there's no method at all on IEnumerable<out T>
that ever takes T
as a parameter, so you can never incorrectly pass a Banana
into IEnumerable<Fruit>
.
Contravariance is much the same but I always forget the exact details on it. Unsurprisingly, for that there's now the in
keyword on type parameters.
Cannot add a derived class object to a list of it's base class type
One possible solution would be to introduce a covariant interface:
public interface IBase<out T> where T : Model
{
T Data { get; }
}
public abstract class BaseClass<T> : IBase<T> where T : Model
Then use the interface for your list:
var myBaseList = new List<IBase<Model>>();
myBaseList.Add(new BaseClassA(new ModelA()));
myBaseList.Add(new BaseClassB(new ModelB()));
Because IBase
declares Data
as get-only, this ensures type-safety.
And here you still have access to Data
:
foreach (var item in myBaseList)
{
if (item.Data.Id > 0)
{
//do stuff
}
}
Cannot convert from ListDerivedClass to ListBaseClass
It is because List<T>
is invariant, not covariant, so you should change to IEnumerable<T>
which supports covariant:
IEnumerable<BaseClass> bcl = new List<DerivedClass>();
public void doSomething(IEnumerable<BaseClass> bc)
{
// do something with bc
}
Information about covariant in generic.
Convert ListDerivedClass to ListBaseClass
The way to make this work is to iterate over the list and cast the elements. This can be done using ConvertAll:
List<A> listOfA = new List<C>().ConvertAll(x => (A)x);
You could also use Linq:
List<A> listOfA = new List<C>().Cast<A>().ToList();
Convert List of Base Class to a List of Derived Class - possible?
Cast<T>
method applies cast operation to all elements of the input sequence. It works only if you can do the following to each element of the sequence without causing an exception:
ParentClass p = ...
ChildClass c = (ChildClass)p;
This will not work unless p
is assigned an instance of ChildClass
or one of its subclasses. It appears that in your case the data returned from the server API contains objects of ParentClass
or one of its subclasses other than ChildClass
.
You can fix this problem by constructing ChildClass
instances, assuming that you have enough information from the server:
List<ChildClass> childList = parentList
.Select(parent => new ChildClass(parent.Name, ... /* the remaining fields */))
.ToList();
Casting List of Derived class to List of base class
The reason you cannot do this is because a list is writable. Suppose it were legal, and see what goes wrong:
List<Cat> cats = new List<Cat>();
List<Animal> animals = cats; // Trouble brewing...
animals.Add(new Dog()); // hey, we just added a dog to a list of cats...
cats[0].Speak(); // Woof!
Well dog my cats, that is badness.
The feature you want is called "generic covariance" and it is supported in C# 4 for interfaces that are known to be safe. IEnumerable<T>
does not have any way to write to the sequence, so it is safe.
class Animal
{
public virtual void Play(IEnumerable<Animal> animals) { }
}
class Cat : Animal
{
public override void Play(IEnumerable<Animal> animals) { }
}
class Program
{
static void Main()
{
Cat cat = new Cat();
cat.Play(new List<Cat>());
}
}
That will work in C# 4 because List<Cat>
is convertible to IEnumerable<Cat>
, which is convertible to IEnumerable<Animal>
. There is no way that Play can use IEnumerable<Animal>
to add a dog to something that is actually a list of cats.
C# create list of generic items to pass to function that takes base class
Since DerivedItem1 inherits from IBaseItemClass but myList.Add() expects List of IBaseItemClass.
And you are giving it a List of DerivedItem1 that doesn't inherits from List of IBaseItemClass anyways.
Here is a solution, not an ideal one but a workaround, try adding like this, it should work:
myList.Add(derivedItemList.Select(item => item as IBaseItemClass).ToList());
Or simply use the IEnumerable extension method Cast as suggested by @juharr in the comments as:
myList.Add(derivedItemList.Cast<IBaseItemClass>().ToList());
Related Topics
Inheritance with Base Class Constructor with Parameters
Force Download of a File on Web Server - Asp .Net C#
What's the Difference Between Returning Void and Returning a Task
Convert Bitmaps to One Multipage Tiff Image in .Net 2.0
Getting the "Diff" Between Two Arrays in C#
Should You Declare Methods Using Overloads or Optional Parameters in C# 4.0
Wpf C# Path: How to Get from a String with Path Data to Geometry in Code (Not in Xaml)
Get Ssid of the Wireless Network I am Connected to with C# .Net on Windows Vista
Inconsistency in Divide-By-Zero Behavior Between Different Value Types
.Net 4.0 and the Dreaded Onuserpreferencechanged Hang
Checking User Name or User Email Already Exists
Why Use Try {} Finally {} with an Empty Try Block
Does Using Tasks (Tpl) Library Make an Application Multithreaded
C# How to Find If an Event Is Hooked Up
How to Return a Custom Http Status Code from a Wcf Rest Method