-3

I am trying to instantiate objects for an array of 10 athletes of the "Sport" abstract class which takes in name and age for each athlete, then there is two derived classes one for "Tennis" athletes and one for "Golf" athletes.

  class Program
    {
        static void Main(string[] args)
        {
            Sport[] athlete = new Sport[10];
            athlete[0] = new Tennis("John Smith", 18, "Tennis", 5.0, 92);
            athlete[1] = new Tennis("Lisa Townsend", 15, "Tennis");
            athlete[2] = new Tennis("Brian Mills", 17, "Tennis", 4.0, 83);
            athlete[3] = new Golf("Stacey Bell", 16, "Golf", 10, 20);
            athlete[4] = new Golf("Tom Spehr", 18, "Golf", 9, 12);
            athlete[5] = new Golf("Sam Calen", 14, "Golf");
            athlete[6] = new Tennis("Karen Strong", 17, "Tennis", 3.0, 78);
            athlete[7] = new Golf("Ken Able", 15, "Golf", 15, 16);
            athlete[8] = new Tennis("Troy Soni", 18, "Tennis", 4.5, 93);
            athlete[9] = new Golf("Toni Palmer", 17, "Golf", 8, 22);

            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("{0}", athlete[i]);
            }    
        }
    }

I am trying to print the array like this but it is not outputting properly. Also when I try to print the data fields individually as

Console.WriteLine("{0} {1}", athlete[i].name, athlete[i].age)

I can get it to output each athletes name and age but if I try to add the other fields they will not output. Should I be declaring each array object as "Sport" rather than Tennis or Golf?

Edit:

Here is the Sport class

abstract class Sport
{

    protected string name;
    protected int age;

    public Sport(string name, int age)
    {
        Name = name;
        Age = age;
    }
    public string Name
    {
        get
        {
            return name;
        }
        set
        {
            name = value;
        }
    }
    public int Age
    {
        get
        {
            return age;
        }
        set
        {
            age = value;
        }
    }
    public abstract void Performance();
 }

and here is the derived Tennis class (the golf class is structured the same way just slight changes to the variable names)

   class Tennis : Sport
    {
        private string type;
        private double rating;
        private int serveSpeed;

        public Tennis(string name, int age, string type, double rating, int serveSpeed) : base(name, age)
        {
            Rating = rating;
            Type = type;
            ServeSpeed = serveSpeed;
        }
        public Tennis(string name, int age, string type) : base(name, age)
        {
        }
        public double Rating
        {
            get
            {
                return rating;
            }
            set
            {
                rating = value;
            }
        }
        public string Type
        {
            get
            {
                return type;
            }
            set
            {
                type = "Tennis";
            }
        }
        public int ServeSpeed
        {
            get
            {
                return serveSpeed;
            }
            set
            {
                serveSpeed = value;
            }
        }
CoreyC
  • 35
  • 8
  • 1
    What do the `Sport`, `Golf`, and `Tennis` classes look like? – Matt Rowland Apr 26 '17 at 15:17
  • 9
    Implement `ToString` for those classes – Jordão Apr 26 '17 at 15:18
  • 2
    "Not outputting properly" tells us nothing. What do you expect? What do you observe? Why do you believe that what you expected is correct? – Eric Lippert Apr 26 '17 at 15:18
  • 8
    Also, I note that storing people's ages is a really bad idea because they change. Store people's *birthdays*, which do not change, and compute their ages. – Eric Lippert Apr 26 '17 at 15:19
  • Also, since Eric started doing a code review, why bother storing "Tennis" inside the `Tennis` object and "Golf" inside a `Golf` object? They already know themselves what they are. – DavidG Apr 26 '17 at 15:20
  • It just needs to print the "Type" of sport the athlete plays – CoreyC Apr 26 '17 at 15:22
  • 1
    Those "type" setters are ... interesting. I've never seen this particular error before. Can you explain what you were thinking here? I am interested to learn how people form wrong intuitions about programming language constructs. – Eric Lippert Apr 26 '17 at 15:22
  • 1
    @EricLippert: I came across something even better the other day. Something like: `set { value = "tennis";}` – Chris Apr 26 '17 at 15:25
  • 3
    The correct pattern to express the "type" would be an *abstract read-only property in the base class*, with the concrete derived classes overriding the read-only property. – Eric Lippert Apr 26 '17 at 15:25
  • The type is just for the output to print what "Type" of athlete they are, I added that get/set for the type after I'm not sure if thats even necessary. – CoreyC Apr 26 '17 at 15:25
  • If you desire to format the output on a per-type basis then *why have you not overridden ToString*? – Eric Lippert Apr 26 '17 at 15:26
  • @EricLippert when do we get a "This code doesn't seem quite correct." compiler warning – Rand Random Apr 26 '17 at 15:27
  • My main question is should I be declaring each object in the array as Sport or Tennis/Golf. – CoreyC Apr 26 '17 at 15:27
  • Then pay attention to the warning and fix the code so that it is correct! – Eric Lippert Apr 26 '17 at 15:27
  • But Sport is an abstract type; it may only be instantiated by instantiating a concrete derived type. – Eric Lippert Apr 26 '17 at 15:28
  • `should I be declaring each object in the array as Sport or Tennis/Golf` you seem to have a misunderstanding. Every element of the array is declared as Sport. They are all Sports. When you are doing `new Golf` or `new Tennis` you are creating an object to put into that array. You cannot create an object of type `Sport` because it is an abstract class so you have no choice but to write the code you have there (or rewrite your classes). – Chris Apr 26 '17 at 15:38

2 Answers2

10

Let's fix this whole thing. Now is the time in your C# career to learn good habits and practices.

abstract class Sport

Sport? No. This class doesn't represent sports. This class represents players. Sports are what they play. The fact that you named the local "athlete" in your Main is telling you that you made a mistake here. Call this Athlete or Player or something. Let's say Player, because then the derived classes can be TennisPlayer and GolfPlayer or Golfer.

protected string name;
protected int age;

Why protected? You have public getters and setters wrapping these!

Eliminate the backing fields entirely.

public string Name
{
    get
    {
        return name;
    }
    set
    {
        name = value;
    }
}

These are needlessly wordy; use auto properties.

public int Age

Ages change constantly; birthdays do not. Store the birthday, and then see

How to calculate an age based on a birthday?

public abstract void Performance();

I don't know what this means but it is probably wrong. Abstract methods are usually verbs, but this is a noun. Shouldn't this be Perform? or Play ? You omit this from your derived classes, so let's ignore it.

Also we want to say what sport we play. So let's make a type for that. Put it all together:

enum Sport { Tennis, Golf }      
public abstract class Player 
{
  // Consider making Name and Birthday get-only as well.
  // Is there any situation in which they change after construction?
  // If not, then *don't allow them to change*!
  public string Name { get; set; }
  public DateTime Birthday { get; set; }
  public abstract Sport Sport { get; }
  public Player(string name, DateTime birthday) 
  {
    this.Name = name;
    this.Birthday = birthday;
  }
}

Much shorter and easier to understand. Now let's derive some types. Again we will shorten up the properties, and add a ToString.

Also, is Rating really a double? Use doubles for physical quantities like height or weight. I suspect this is a decimal, not a double. If it is absolutely terribly wrong for a rating of 3.4 to actually be stored as 3.39999999999999999, then double is the wrong type to use and decimal is the right type to use.

public sealed class TennisPlayer : Player
{
  public override Sport Sport => Sport.Tennis;
  // Again, do these change after construction? If not
  // then remove the setters.
  public decimal Rating { get; set; }
  public int ServeSpeed { get; set; }
  public TennisPlayer(string name, DateTime birthday, decimal rating, int speed) : base(name, birthday) 
  {
    this.Rating = rating;
    this.ServeSpeed = speed;
  }
  public override string ToString() 
  {
    return @"Tennis player: {Name} {Birthday} {Rating} {ServeSpeed}";
  }
}

Again, so much shorter and easier to read. Now you do the same thing for GolfPlayer or Golfer or whatever you call it.

Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Ok, thank you for the input I appreciate it I will use it to edit thanks – CoreyC Apr 26 '17 at 17:14
  • Why do you use the redundant `this` keyword in the constructors to assign properties? Is it considered a best practice, even when it is not needed? – Luca Cremonesi Apr 26 '17 at 19:59
  • 1
    @LucaCremonesi: When I have two things that are similarly named and one is a member and one is a local, I prefer to call out that the thing that is a member is a member. It helps me avoid silly mistakes. If you're the sort of person who doesn't make silly mistakes or doesn't mind when you make them, then go ahead and omit it. – Eric Lippert Apr 26 '17 at 21:02
  • I wish Eric corrected me like this when I was learning to code lol – Harry Apr 28 '17 at 15:49
0

If I understand your question correctly, you want to be able to output a friendly string for your classes, where they always output Name and Age, and then for each sport they output specific properties of that sport.

This can be done by overriding the ToString property. You can do this in the base class for Name and Age, and then in each individual class you can output base.ToString along with any specific class properties.

For example, the base class would output Name and Age:

abstract class Sport
{
    public override string ToString()
    {
        return string.Format("{0} {1}", Name, Age);
    }

    // Rest of class code omitted...

And the Tennis class will output some other fields as well:

class Tennis : Sport
{
    public override string ToString()
    {
        return string.Format("{0} [Sport: {1}] [Rating: {2}] [Serve Speed: {3}]",
            base.ToString(), Type, Rating, ServeSpeed);
    }

    // Rest of class code omitted...

Then your output will look like this:

enter image description here

Also, notice that the Sport string is empty for Lisa Townsend because the constructor that was used to instantiate that instance does not set the Type property.

Rufus L
  • 36,127
  • 5
  • 30
  • 43