For your problem there are several native C# possibilities:
IReadOnlyList
, IEnumerable
=> error free and typesafe. What you probably need.
- Use
List<>
the proper way => error free / not typesafe
Array
=> typesafe / throws runtime errors
dynamic
or List<object>
=> universal / not typesafe / throws runtime errors
All of them work well! There is no need for any tricky programming!
Interface and class definitions for all of the examples:
using System; using System.Collections.Generic; using System.Linq;
interface IAnimal
{
public string Name { get; }
}
class Bear : IAnimal
{
public string BearName = "aBear";
public string Name => BearName;
}
class Cat : IAnimal
{
public string CatName = "aCat";
public string Name => CatName;
}
// Dog has no base class/interface; it isn't related to the other classes
// But it also provides a <Name> property (what a coincidence!)
class Dog
{
public string DogName = "aDog";
public string Name => DogName;
}
public class AssignDerivedClass {
private static string AnyName(dynamic anymal) => anymal switch
{
IAnimal animal => animal.Name,
Dog dog => dog.DogName,
string s => s,
_ => "Any unknown Animal"
};
Here are examples for each of the solutions:
1. IReadOnlyList
, IEnumerable
: What you probably need
- Assign your
List<C>
to an IEnumerable<A>
or IReadOnlyList<A>
- Neither of them can be changed at runtime, i.e. you can't
Add
or Remove
elements.
- You can still modify your data elements themselves.
- Using this
IEnumerable
or IReadOnlyList
is very cheap. Just accessing your data through another pointer.
- But take care: If you
Append
elements to IEnumerable or IReadOnlyList, then you create a new IEnumerable
. Using this IEnumerable
improperly may become expensive.
Since no elements can be added, all elements remain of correct type:
public static void TestIEnumerableAndIReadonlyList()
{
var cats = new List<Cat>()
{
new Cat() { CatName = "Cat-3" },
};
IEnumerable<IAnimal> animalsEnumerable = cats;
IReadOnlyList<IAnimal> animalsReadOnlyList = cats;
var extendedEnumerable = animalsReadOnlyList.Append(new Bear());
(extendedEnumerable.First() as Cat).CatName = "Cat-3a";
Console.WriteLine("Cat names: {0}, {1}, {2}, {3}",
cats.ElementAt(0).CatName,
animalsReadOnlyList[^1].Name,
AnyName(animalsEnumerable.Last()),
AnyName(extendedEnumerable.ElementAt(1)));
}
// Result => Cat names: Cat-3a, Cat-3a, Cat-3a, aBear
2. Use List<>
the proper way: List<A> listOfA = new()
- Define a List of your interface (not of a derived class)
- Assign instances of one derived class only - you didn't want to store other classes anyway, did you?
Adding a Cat to a List of Animals which are supposed to be Bears:
public static void TestListOfInterface()
{
var bears = new List<IAnimal>()
{
new Bear() { BearName = "Bear-1" },
};
bears.Add(new Cat() { CatName = "Cat-2" });
string bearNames = string.Join(", ", bears.Select(animal => animal.Name));
Console.WriteLine($"Bear names: {bearNames}");
static string VerifyBear(IAnimal bear)
=> (bear as Bear)?.Name ?? "disguised as a bear!!!";
string bearInfo0 = VerifyBear(bears[0]);
string bearInfo1 = VerifyBear(bears[1]);
Console.WriteLine($"One animal is {bearInfo0}, the other one is {bearInfo1}");
}
// Bear names: Bear-1, Cat-2
// One animal is Bear-1, the other one is disguised as a bear!!!
3. Array
: Creating a Bear[]
array, it is guaranteed that all array elements reference instances of Bear
.
- You can exchange elements, but you can't remove or add new elements.
- Trying to set a wrong type yields a runtime error.
Bears in an array:
public static void TestArray()
{
Bear[] bears = { new Bear(), null };
IAnimal[] bearAnimals = bears;
try { bearAnimals[1] = new Cat(); } // => ArrayTypeMismatchException
catch (Exception e) { Console.Error.WriteLine(e); } // type incompatible with array
bearAnimals[1] = new Bear() { BearName = "Bear-2" };
Console.WriteLine($"Bear names: {bearAnimals[0].Name}, {bears[1].BearName}");
}
// Result => Bear names: aBear, Bear-2
4. dynamic
and List<dynamic>
: The most universal solution
- Type checking at runtime
- You abandon your compiler error checking support, so handle with care!
- If you try to add an element of wrong type, you'll only get a runtime error!
- If you access a non-existing member, you'll only get a runtime error!
- You can even assign collections of unrelated classes.
Assign your list to dynamic
or use List<dynamic>
:
public static void TestDynamicListAndArray()
{
dynamic any = new List<Cat>() // List of specific class - or of interface
{
new Cat() { CatName = "Cat-1" },
new Cat() { CatName = "Cat-2" },
};
try { any[0].BearName = "Bear-1"; } // => RuntimeBinderException
catch (Exception e) { Console.Error.WriteLine(e); } // Cat has no BearName
try { any.Add(new Bear()); } // => RuntimeBinderException
catch (Exception e) { Console.Error.WriteLine(e); } // no matching overload
any[1].CatName += 'a';
Console.WriteLine($"Animal names: {any[0].CatName}, {any[1].Name}");
var mix = new List<dynamic>
{
new Bear() {BearName = "Bear-3"},
new Dog() {DogName = "Dog-4"},
"Cat-5", // 100MHz ;-)
};
Console.WriteLine($"{AnyName(mix[0])}, {mix[1].Name}, {AnyName(mix[2])}");
try { Console.WriteLine($"Names: {any[2].Name}"); } // => RuntimeBinderException
catch (Exception e) { Console.Error.WriteLine(e); } // no definition for 'Name'
any = new Bear() { BearName = "Bear-6" }; // Scalar - not a List or Array!
try { Console.WriteLine($"{AnyName(any[0])}"); } // => RuntimeBinderException
catch (Exception e) { Console.Error.WriteLine(e); } // cannot apply indexing []
}
//Animal names: Bear-1, Cat-2a
//Bear-3, Dog-4, Cat-5
} //end of class AssignDerivedClass