André Lopes
André Lopes

Reputation: 11

Access information in a List of a abstract class

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

Answers (2)

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

Dai
Dai

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:

Approach 1: Using 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.

Approach 2: Using custom Match/Switch method:

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

Related Questions