How to Write C# OOP Code — BPL Cricket System
Step-by-step: from reading the question → planning → writing every line of code
What to do & why Code to write
PHASE 1 — READ THE QUESTION AND EXTRACT REQUIREMENTS
What is the question asking? From the question, extract 4 things:
Read every sentence. Underline: class names, fields, conditions, 1. Classes: Player, Batsman, Bowler, AllRounder
rules. 2. Fields: runs, avg, highScore, wickets, bowlingAvg
3. Method: PlayerPerformance() → returns bool (eligible or not)
4. Rules:
Batsman: avg > 50
Bowler: wickets > 100
AllRounder: runs > 1000 AND wickets > 50
PHASE 2 — PLAN THE CLASS HIERARCHY BEFORE TOUCHING THE KEYBOARD
Why plan first? Hierarchy map:
If you write code before planning, you'll miss which methods go in Player (abstract base)
which class. ├── fields: PlayerId, PlayerName, TeamName (shared)
Rule of thumb: ├── ShowInfo() → virtual (base prints id/name/team)
└── PlayerPerformance() → abstract (no body here!)
If something is SHARED by all → it goes in the BASE class.
If it's UNIQUE to one type → it goes in THAT subclass. Batsman : Player
│ TotalRuns, BattingAverage, HighestScore
│ PlayerPerformance() → BattingAverage > 50
Bowler : Player
│ TotalWickets, BowlingAverage
│ PlayerPerformance() → TotalWickets > 100
AllRounder : Player
TotalRuns, TotalWickets
PlayerPerformance() → runs>1000 AND wickets>50
PHASE 3 — WRITE THE CODE, ONE PIECE AT A TIME
STEP 1 Start with namespace and using statement
Always the outermost wrapper of any C# program
Why namespace?
A namespace groups your classes together and avoids name
conflicts with other libraries.
using System is needed for [Link]. Without it,
the compiler can't find Console.
using System;
namespace ConsoleAppCricketWorldCup {
// ... all classes go inside here
}
STEP 2 Write the abstract Player base class
abstract = cannot be instantiated. Contains SHARED fields and the abstract method contract.
Why abstract?
abstract means you can NEVER do new Player() directly. A
Player alone makes no sense — you need a specific type.
Auto-properties:
{ get; set; } is shorthand. C# auto-generates the hidden
backing field for you.
Constructor tip:
The base constructor takes id, name, team and assigns them.
Subclasses will call this with : base(...).
// Step 2a — class header with 'abstract' keyword
public abstract class Player {
// Step 2b — shared properties (every player has these)
public string PlayerId { get; set; }
public string PlayerName { get; set; }
public string TeamName { get; set; }
// Step 2c — constructor: assign all three shared fields
public Player(string id, string name, string team) {
PlayerId = id; PlayerName = name; TeamName = team;
}
// Step 2d — virtual ShowInfo: base prints id/name/team
// 'virtual' = subclasses CAN override it
public virtual void ShowInfo() {
[Link]($" ID: {PlayerId} Name: {PlayerName} Team: {TeamName}");
}
// Step 2e — abstract PlayerPerformance: NO body here!
// 'abstract' = subclasses MUST override and implement it
public abstract bool PlayerPerformance();
}
RULE: abstract method has no body (no { }). It's a PROMISE that every subclass will provide their own version. If a subclass doesn't override it, the code won't compile.
vs virtual: virtual has a body (a default implementation). Subclasses CAN override it but don't have to. Here ShowInfo() is virtual because the base prints 3 lines that every subclass
reuses.
STEP 3 Write the Batsman subclass
Adds its own fields, calls base constructor, overrides both methods
Pattern for every subclass:
① class Name : Player
② add its OWN fields
③ constructor calls : base(id, name, team)
④ override ShowInfo() — print [ TYPE ] then call
[Link]() then print own fields
⑤ override PlayerPerformance() with the
eligibility condition
The : base() trick:
Instead of re-assigning PlayerId, PlayerName, TeamName yourself,
you hand them to the parent constructor with : base(id, name,
team).
// Step 3a — inherit from Player
public class Batsman : Player {
// Step 3b — Batsman-specific fields
public double TotalRuns { get; set; }
public double BattingAverage { get; set; }
public double HighestScore { get; set; }
// Step 3c — constructor: take ALL params, pass shared ones to base
public Batsman(string id, string name, string team,
double runs, double avg, double high)
: base(id, name, team) { // ← hands id/name/team to Player constructor
TotalRuns = runs; BattingAverage = avg; HighestScore = high;
}
// Step 3d — override ShowInfo: print type label, call base, print own fields
public override void ShowInfo() {
[Link]("[ BATSMAN ]");
[Link](); // ← runs [Link]() → prints id/name/team
[Link]($" Runs: {TotalRuns} Avg: {BattingAverage} Highest: {HighestScore}");
[Link](PlayerPerformance()
? " Award: ELIGIBLE (Batting Average > 50)"
: " Award: Not Eligible");
}
// Step 3e — override PlayerPerformance: the eligibility rule
// => means 'expression body' — shorthand for { return ...; }
public override bool PlayerPerformance() => BattingAverage > 50;
}
KEY CONCEPT: [Link]() By calling [Link]() inside the override, you REUSE the parent's code. You don't retype the ID/Name/Team lines. This is the whole point of
inheritance — write once, reuse everywhere.
KEY CONCEPT: => (expression body) public override bool PlayerPerformance() => BattingAverage > 50; is identical to writing { return BattingAverage > 50; } — just shorter. Use it
when the whole method is a single expression.
STEP 4 Write the Bowler subclass
Same pattern as Batsman — only fields and condition change
Bowler only has 2 own fields:
TotalWickets and BowlingAverage. Notice no HighestScore
— that was Batsman-specific.
Condition: TotalWickets > 100
The logic is the same pattern. Only the field name and number
change.
public class Bowler : Player {
public double TotalWickets { get; set; }
public double BowlingAverage { get; set; }
// Only 2 own params after the 3 shared ones
public Bowler(string id, string name, string team,
double wkts, double avg)
: base(id, name, team) {
TotalWickets = wkts; BowlingAverage = avg;
}
public override void ShowInfo() {
[Link]("[ BOWLER ]");
[Link]();
[Link]($" Wickets: {TotalWickets} Bowling Avg: {BowlingAverage}");
[Link](PlayerPerformance()
? " Award: ELIGIBLE (Wickets > 100)"
: " Award: Not Eligible");
}
// Condition: wickets > 100
public override bool PlayerPerformance() => TotalWickets > 100;
}
STEP 5 Write the AllRounder subclass
Two conditions joined by && — both must be true
AND condition:
The question says runs > 1000 AND wickets > 50. In C#, AND =
&&. Both must be true for the method to return true.
AllRounder has NO BattingAverage or BowlingAverage.
The question only says runs + wickets. Don't add fields the question
didn't ask for.
public class AllRounder : Player {
// AllRounder tracks runs AND wickets (no average fields)
public double TotalRuns { get; set; }
public double TotalWickets { get; set; }
public AllRounder(string id, string name, string team,
double runs, double wkts)
: base(id, name, team) {
TotalRuns = runs; TotalWickets = wkts;
}
public override void ShowInfo() {
[Link]("[ ALL-ROUNDER ]");
[Link]();
[Link]($" Runs: {TotalRuns} Wickets: {TotalWickets}");
[Link](PlayerPerformance()
? " Award: ELIGIBLE (Runs>1000 AND Wickets>50)"
: " Award: Not Eligible");
}
// && means BOTH conditions must be true
// Sam Curran: runs=781 (fails runs>1000) → Not Eligible even though wickets=60>50
public override bool PlayerPerformance() =>
TotalRuns > 1000 && TotalWickets > 50;
}
PITFALL: && vs || AND (&&): BOTH must be true. Sam Curran has 781 runs (fails runs>1000) so he is NOT eligible even though 60>50 passes. OR (||): EITHER one being true is
enough. This question wants &&, so both must pass.
STEP 6 Write the Main method — create objects and loop
Player[] array holds all 4 players. foreach calls ShowInfo() on each — polymorphism picks the right version automatically.
Player[] array:
Player[] means an array that holds references to Player objects.
But since Batsman, Bowler, AllRounder all inherit Player, they all fit
in this array.
Polymorphism in foreach:
[Link]() — even though player is declared as type
Player, C# looks at the actual object type at runtime and calls the
RIGHT ShowInfo(). This is polymorphism.
Argument order MUST match constructor:
new Batsman(id, name, team, runs, avg, high) — count your
arguments against the constructor signature.
class Program {
static void Main(string[] args) {
// Step 6a — declare array of base type Player (size 4)
Player[] players = new Player[4];
// Step 6b — assign each slot. Arguments follow constructor order.
// Batsman(id, name, team, runs, avg, highScore)
players[0] = new Batsman("P-1", "Tom Latham", "NZ", 6789, 57.3, 183);
// Bowler(id, name, team, wickets, bowlingAvg)
players[1] = new Bowler("P-2", "Taskin Ahmed", "BD", 104, 23.2);
// AllRounder(id, name, team, runs, wickets)
players[2] = new AllRounder("P-3", "Glenn Maxwell", "AUS", 7590, 98);
players[3] = new AllRounder("P-4", "Sam Curran", "Eng", 781, 60);
// Step 6c — foreach: C# automatically calls the right ShowInfo()
// players[0] is a Batsman → calls [Link]()
// players[1] is a Bowler → calls [Link]()
// This is POLYMORPHISM — one loop handles all types
foreach (Player player in players) {
[Link]();
[Link](); // blank line between players
}
}
}
POLYMORPHISM in action: foreach (Player player in players) — [Link]() looks like it would call [Link](). But it actually calls WHICHEVER subclass the object really
is. Batsman at index 0 → [Link]() runs. C# figures this out automatically at runtime. This is why we don't need 4 separate loops.
STEP 7 Close the namespace — final bracket
Every opening { needs a closing }. Check your brackets.
} // closes: namespace ConsoleAppCricketWorldCup
EXPECTED OUTPUT — verify your code produces this exactly
[ BATSMAN ]
ID: P-1 Name: Tom Latham Team: NZ
Runs: 6789 Avg: 57.3 Highest: 183
Award: ELIGIBLE (Batting Average > 50)
[ BOWLER ]
ID: P-2 Name: Taskin Ahmed Team: BD
Wickets: 104 Bowling Avg: 23.2
Award: ELIGIBLE (Wickets > 100)
[ ALL-ROUNDER ]
ID: P-3 Name: Glenn Maxwell Team: AUS
Runs: 7590 Wickets: 98
Award: ELIGIBLE (Runs>1000 AND Wickets>50)
[ ALL-ROUNDER ]
ID: P-4 Name: Sam Curran Team: Eng
Runs: 781 Wickets: 60
Award: Not Eligible ← 781 fails runs>1000
QUICK REFERENCE — C# OOP keywords for exam questions like this
abstract class → Cannot be instantiated. Must be inherited. Contains abstract methods (no body).
: Player → Inherits from Player. Child gets all parent fields and methods.
: base(id,name,team) → Calls the parent constructor. Do this whenever parent has a constructor with params.
virtual → Method HAS a body in parent. Child CAN override it. If not overridden, parent version runs.
abstract method → Method has NO body in parent. Child MUST override it or won't compile.
override → Child is replacing/extending the parent version of a virtual or abstract method.
[Link]() → Explicitly calls the parent's version. Lets you reuse parent code + add your own.
Player[] array → Array of base type. Can hold any subclass (Batsman, Bowler, AllRounder) because all inherit Player.
foreach polymorphism → [Link]() calls the ACTUAL type's method automatically at runtime.
=> expression body → Shorthand for single-line methods. => BattingAverage > 50 means { return BattingAverage > 50; }
&& → AND — both conditions must be true. Runs>1000 && Wickets>50 both need to pass.