Reputation: 11
I have the code displayed below. I created a list of animals ("List< Animals >) where I added several Cat, Dog and Bird. Is it possible to access to the different fields, like a dogs' age or a cats' color, of each class directly by that list?
Thank you!
public abstract class Animals
{
}
public class Cat : Animals
{
public string Name;
public string Color;
public int Age;
public string Breed
}
public class Dog : Animals
{
public string Name;
public string Color;
public int Age;
public string Breed
}
public class Bird : Animals
{
public string Name;
public int Age;
public string Wing_Color;
public string Fly_Height;
}
Upvotes: 1
Views: 199
Reputation: 48307
since all your animals have a name and age I would suggest to use the nicer design and move those fields to the abstract parent class and define abstract getters for them:
public abstract class Animal
{
public string Name;
public int Age;
abstract string getName();
abstract int getAge();
}
public class Cat : Animal
{
public string Breed
}
public class Dog : Animal
{
public string Color;
public string Breed
}
public class Bird : Animal
{
public string Wing_Color;
public string Fly_Height;
}
Upvotes: 0
Reputation: 155708
Is it possible to access to the different fields, like a dogs' age or a cats' color, of each class directly by that list?
Yes, but only if you know the types at compile-time.
C# does not (yet) support proper algebraic-typing or discriminated-unions, so for exhaustive checking you'll need to either use the is
operator (ideally with a Code Analysis package like ExhaustiveMatching) or define your own Match
method.
Like so:
is
operator:This use of the is
operator is sometimes referred to as "pattern matching" - but I disagree with that term because it's really just an ergonomic improvement over the syntax for runtime type-checking rather than real (in the Haskell sense) pattern-matching over data.
List<Animal> animals = ...
foreach( Animal a in animals )
{
if( a is Cat cat )
{
Console.WriteLine( "Cat breed: {0}.", cat.Breed );
}
else if( a is Dog dog )
{
Console.WriteLine( "Dog breed: {0}.", dog.Breed );
}
else if( a is Bird bird )
{
Console.WriteLine( "Bird name: {0}.", bird.Name );
}
else
{
throw new InvalidOperationException( "Unknown animal subclass." );
}
}
Code Analysis packages like ExhaustiveMatching will give you compile-time warnings or errors if you have a closed type hierarchy defined (like Java-style enum classes) that you're switching over (using C# 7.x switch
statement with case TypeName name:
-syntax) but with absent subclass cases.
Defining a custom Match
or Switch
method allows you to require consuming call-sites to be exhaustive, but this is contingent on your Match
/Switch
method being exhaustive - but as it moves the burden-of-responsibility to you - rather than the consumer - this approach has advantages.
Like so:
abstract class Animal
{
public TResult Match<TResult>(
Func<Cat ,TResult> isCat,
Func<Dog ,TResult> isDog,
Func<Bird,TResult> isBird
)
{
if ( this is Cat c ) return isCat( c );
else if( this is Dog d ) return isDog( d );
else if( this is Bird b ) return isBird( b );
else throw new InvalidOperationException( "Unknown animal subclass." );
}
}
Used like so:
foreach( Animal a in animals )
{
String summary = a.Match(
isCat : c => "Cat breed: " + c.Breed,
isDog : d => "Dog breed: " + d.Breed,
isBird: b => "Bird name: " + b.Name,
);
Console.WriteLine( summary );
}
Upvotes: 1