C# Programming · Lesson 11
Showing Some Class
Model real-world concepts with C# classes, objects, properties, references, nested classes, static members, const, and readonly.
Lesson purpose
Lesson 11 opens Part 2 of the course, object-oriented programming. Intrinsic types such as int, double, and bool hold single values, and arrays and collections group values of one type. Real programs also need to bundle unlike data that belongs together: a student has a string name, a double grade-point average, and a long serial number. In C# the class is the structure for that bundle. Students get a quick overview of OOP principles and access modifiers, then define a class, create objects with new, access members with the dot operator, use the C# 9.0 target-typed new() with object initializers, keep objects separate, reason about references and null, build classes that contain classes, and distinguish static members, instance members, const, and readonly.
Learning objectives
- Explain why programs need a class to bundle related data of different types.
- Define abstraction, encapsulation, reuse (DRY), inheritance, and polymorphism in plain language.
- Describe the
public,protected,private, andinternalaccess modifiers. - Identify the methods, properties, and events that make up a class.
- Define a class with auto-implemented properties and explain the difference between properties and fields.
- Declare a reference variable and create an object with
new. - Access and assign members with the dot operator.
- Create objects with the traditional approach and with the C# 9.0 target-typed
new()and object initializers. - Explain why separate objects keep separate data, and why two references can share one object.
- Describe
nullreferences and garbage collection. - Build a class that contains another class and access nested members.
- Distinguish static (class) members from instance members.
- Compare
constandreadonlydata members.
1. Why classes
Arrays and collections gather like-typed values, such as a string[] of student names. But a student is more than a name. A college enrollment application tracks each student’s name (string), grade-point average (double), and serial number (long). Those three different types logically belong together in one structure named Student. C# provides the class for groupings of unlike-typed variables.
2. A quick overview of object-oriented programming
Part 1 used procedural techniques. Object-oriented programming (OOP) is another way to write applications whose purpose is to model the real world in code. A Student class is a code model of a real student, limited to what the program needs: it would not include eating preferences unless that is what the program models.
Core principles
| Principle | Meaning |
|---|---|
| Abstraction | Focus on what is needed in the real world rather than on the underlying programming details. Working with a Student means thinking about a student, not the code inside. |
| Encapsulation | Keep data and code together so everything needed is in one self-contained place. |
| Reuse (DRY) | Write a class once and use it for every object that fits its model: Don’t Repeat Yourself. |
| Inheritance | Build a class hierarchy. Car, Truck, and Bus derive from Vehicle and inherit its features, and Car can be broken down further into Coupe, Sedan, and Racer. |
| Polymorphism | Further define inherited features in a subclass. Every vehicle has a roof, so Vehicle defines a Roof property, Car defines it further, and each car type adds specifics: “one thing is like another thing, but with these changes.” |
Inheritance and polymorphism are developed in Lessons 15 and 16.
Keeping objects safe: access modifiers
Access modifiers state what users of a class can and cannot reach. They keep implementation details private and let the developer change the code without changing the class’s interface.
| Modifier | Accessible from |
|---|---|
public |
Any other class |
protected |
The same class and its child classes (a Car can use Vehicle’s protected members; a Road class cannot) |
private |
Only the same class |
internal |
Anywhere within the same assembly (the application), but not from other assemblies |
Combinations such as protected internal exist but are beyond this course. Lesson 14 covers access control in detail.
Classes and objects
A class is a blueprint; an object is one thing built from it. Creating an object from a class is called instantiating it. Mike and Sally can both be instances of Student: the same structure, different details (Name is “Mike” in one and “Sally” in the other).
A class contains:
- Methods: what you can do with the object, such as changing a name or GPA. Methods give a consistent way to interact with objects (Lesson 12).
- Properties: the object’s characteristics, such as name or GPA. Every object has the same properties with its own values, and a property lets you think “Name” instead of “a string.”
- Events: alerts to anyone listening that something changed in the object, such as a new GPA (Lesson 18).
Quick check: which principle does a subclass use when it redefines an inherited Roof property?
3. Defining a class
A class bundles unlike data and functions that logically belong together into one tidy package. Good classes represent concepts from the problem: analysts say a class “maps concepts from the problem into the program.” A traffic simulator will certainly involve vehicles, which have a top speed, a weight, and perhaps a clunker status, so the program needs a Vehicle class.
public class Vehicle
{
public string model { get; set; } // Name of the model
public string manufacturer { get; set; } // Name of the manufacturer
public int numOfDoors { get; set; } // Number of vehicle doors
public int numOfWheels { get; set; } // Number of wheels
}
public: the access modifier; the class is available throughout the program.class: the kind of structure being created.Vehicle: the class name. Names are case-sensitive, and by convention a class name starts with a capital letter.- Members: the items inside the braces that make up the class. A class can have zero or more members.
{ get; set; }: makes each member an auto-implemented property that can be read and written.- Give members descriptive names, and add comments when they clarify purpose.
In a traditional program, place the class definition before class Program in the file.
Naming convention: the book uses lowercase property names such as model. Standard C# style capitalizes properties (Model, Manufacturer, NumOfDoors); the classroom convention is PascalCase for new code, while the examples here keep the book’s names so they match the source files.
Properties versus fields
Many online examples call these properties when they are really fields:
public class Vehicle
{
public string model; // a field, not a property
public string manufacturer;
public int numOfDoors;
public int numOfWheels;
}
| Field | Property | |
|---|---|---|
| Declaration | public string model; |
public string model { get; set; } |
| Exposes implementation | Yes; outside code touches the variable directly | No; access goes through accessors |
| Validation and formatting | Not possible | Accessors can validate input and format output |
| Visual Studio reference count | Not shown | Shown above the declaration |
Public fields break abstraction because they expose the implementation. Properties cost only a few extra keystrokes and give far more control, so the classroom convention is properties for public data.
4. Creating an object
Designing a vehicle is not the same as building a car. A class declaration describes; an object exists.
Vehicle myCar;
myCar = new Vehicle();
- The first line declares a variable
myCarof typeVehicle, just asint num;declares anint. A class is a type. new Vehicle()instantiates a newVehicleobject: it allocates a block of memory for the object’s properties.- The assignment stores the object’s memory location in
myCar.newhas nothing to do with the car’s age.
The term object means a “thing”: an int variable is an int object, and a car is a Vehicle object.
Memory model: a value-type variable such as int num = 1; holds the value itself, and local value types live on the stack. new Vehicle() allocates the object on the heap, and myCar holds a reference to that memory, not the data itself.
5. Accessing members with the dot operator
Each object has its own set of members. The dot operator selects a member of a particular object:
myCar.numOfDoors = 1;
myCar.manufacturer = "BMW";
myCar.model = "Isetta";
Every C# operation is checked by type as well as value. myCar.numOfDoors is an int and the constant 1 is an int, so the assignment matches. (The 1950s Isetta had a single door that opened the whole front of the car.) The book’s text calls this member numberOfDoors in places; the class defines it as numOfDoors.
6. Working with object-based code
The traditional approach
The VehicleData program defines the class, creates an object, assigns its properties from input, and displays them:
Console.WriteLine("Enter the properties of your vehicle");
Vehicle myCar = new Vehicle();
Console.Write("Model name = ");
string s = Console.ReadLine() ?? "";
myCar.model = s; // through a temporary variable
Console.Write("Manufacturer name = ");
myCar.manufacturer = Console.ReadLine() ?? ""; // directly
Console.Write("Number of doors = ");
myCar.numOfDoors = Convert.ToInt32(Console.ReadLine());
Console.Write("Number of wheels = ");
myCar.numOfWheels = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("\nYour vehicle is a ");
Console.WriteLine(myCar.manufacturer + " " + myCar.model);
Console.WriteLine("with " + myCar.numOfDoors + " doors, riding on "
+ myCar.numOfWheels + " wheels.");
Sample: Metropolitan, Nash, 2, 4 prints “Nash Metropolitan with 2 doors, riding on 4 wheels.” Write keeps the user’s input on the prompt line, and \n inside the string produces a blank line. The input is not validated here but should be (Lessons 3 and 9).
The C# 9.0 approach
VehicleData2 gathers all input first, then creates the object in one expression:
Console.Write("Model name = ");
string s = Console.ReadLine() ?? "";
Console.Write("Manufacturer name = ");
string mfg = Console.ReadLine() ?? "";
Console.Write("Number of doors = ");
int doors = Convert.ToInt32(Console.ReadLine());
Console.Write("Number of wheels = ");
int wheels = Convert.ToInt32(Console.ReadLine());
Vehicle myCar = new()
{
model = s,
manufacturer = mfg,
numOfDoors = doors,
numOfWheels = wheels
};
Console.WriteLine($"\nYour vehicle is a {myCar.manufacturer} {myCar.model} " +
$"with {myCar.numOfDoors} doors riding on {myCar.numOfWheels} wheels");
- Target-typed
new(): the type is omitted afternewbecause the compiler infers it from the declared typeVehicle. - Object initializer: the braces after
new()set properties in a list as part of creation. - One place shows exactly where every value comes from, which is clearer and slightly shorter. Keep the traditional form for compatibility with older code.
7. Discriminating between objects
A program can create many objects of the same class, each with its own data:
Vehicle car1 = new() { manufacturer = "Studebaker", model = "Avanti" };
Vehicle car2 = new() { manufacturer = "Hudson", model = "Hornet" };
// Creating car2 has no effect on car1.
car1 and car2 occupy separate memory locations. Each can be created, changed, and discarded as a single entity without affecting the other. This ability to keep objects distinct is much of the class construct’s power.
8. References
The dot operator and assignment are the basic operations on reference types:
Vehicle yourCar; // declared, but refers to no object yet
yourCar = new Vehicle(); // now refers to a new object
yourCar.manufacturer = "Rambler"; // dot accesses a member
Vehicle yourSpousalCar = yourCar; // a second reference to the SAME object
- A reference that refers to no object is null. Using a
nullreference to reach a member throws an error that terminates the program. The compiler catches most uses of an unassigned local variable, but a reference set tonullcan still fail at run time. - Assigning one reference to another copies the reference, not the object. Both variables now refer to the same memory.
Vehicle yourCar = new Vehicle();
yourCar.model = "Kaiser";
Vehicle yourSpousalCar = yourCar;
yourSpousalCar.model = "Henry J";
Console.WriteLine("Your car is a " + yourCar.model); // Henry J
Changing the object through either reference changes what both see. yourSpousalCar does not point to yourCar; both point to the same vehicle.
Vehicle yourCar = new Vehicle();
yourCar.model = "Kaiser";
Vehicle yourSpousalCar = yourCar;
yourCar = null; // yourCar no longer refers to it
Console.WriteLine("Your car was a " + yourSpousalCar.model); // Kaiser
The object stays reachable as long as any reference to it remains. When no references remain, the garbage collector eventually reclaims its memory for new objects (more in Lesson 15).
Efficiency point: references have a fixed size, so arrays and collections of objects store references. Swapping two array elements moves references, not whole objects (the swap in Lesson 6).
Quick check: after b = a; and b.model = "X";, what is a.model? Contrast this with int y = x; y = 5;.
9. Classes that contain classes
Members can themselves be references to other classes. Adding motor details directly to Vehicle is tempting:
public int power; // horsepower
public double displacement; // liters
But power and displacement describe the motor, not the car. The same Jeep can come with a slow 2.4-liter engine or a peppy 4.0-liter one. A motor is a concept of its own and deserves its own class:
public class Motor
{
public int power { get; set; } // horsepower
public double displacement { get; set; } // liters
}
public class Vehicle
{
public string model { get; set; }
public string manufacturer { get; set; }
public int numOfDoors { get; set; }
public int numOfWheels { get; set; }
public Motor motor { get; set; } // a Vehicle HAS a Motor
}
Nested initializers build the whole hierarchy in one readable declaration:
Vehicle myCar = new()
{
model = "Cherokee Sport",
manufacturer = "Jeep",
numOfDoors = 2,
numOfWheels = 4,
motor = new()
{
power = 230,
displacement = 4.0
}
};
Console.WriteLine($"Your vehicle is a {myCar.manufacturer} {myCar.model} with " +
$"{myCar.numOfDoors} doors riding on {myCar.numOfWheels} wheels using a " +
$"{myCar.motor.displacement} liter engine producing {myCar.motor.power} hp.");
- A
new()inside anew()creates the contained object as part of the outer one. Older C# required creating theMotorfirst and then assigning it. - Nested members are reached with a chain of dots:
myCar.motor.power. - If
motoris never assigned, it isnull, andmyCar.motor.powerfails at run time.
The book shows Motor with fields; the classroom version uses properties to match the rest of the lesson.
10. Static members
Most data members describe one object. Each car has its own license plate:
public class Car
{
public static int numberOfCars; // shared by the class
public string licensePlate; // one per object
}
Car spouseCar = new Car();
spouseCar.licensePlate = "XYZ123";
Car yourCar = new Car();
yourCar.licensePlate = "ABC789";
Car.numberOfCars++; // accessed through the class name
- The number of cars built is a property of the class, not of any single car, so it is marked
static. - Static members are accessed through the class (
Car.numberOfCars), not through an object. There is exactly one copy, shared by everyCar. - Class members are static members. Non-static members, which belong to each individual object, are instance members.
Quick check: given Car newCar = new Car();, is the count reached with newCar.numberOfCars or Car.numberOfCars?
11. const and readonly data members
A const member is a special kind of static member representing a constant. Its value must be set in the declaration and can never change:
class Program
{
public const int daysInYear = 366; // includes leap day; initializer required
public static void Main(string[] args)
{
int[] maxTemperatures = new int[daysInYear];
for (int index = 0; index < daysInYear; index++)
{
// … record the maximum temperature for each day …
}
}
}
A descriptive name such as daysInYear replaces a mysterious number such as 366 and makes the program easier to read and change.
A readonly member also cannot change after it receives its value:
public readonly int daysInYear = 366; // could also be static
const |
readonly |
|
|---|---|---|
| When the value is set | At compile time, in the declaration | In the declaration or in a constructor, at run time |
| Static? | Always, automatically | Instance by default; can be marked static |
| Allowed types | Built-in types known at compile time (numbers, char, bool, string) |
Any type |
| Inside a method | Allowed as a local constant | Not allowed |
The book says readonly is usually preferable to const; the practical guideline is to use const for values that truly never change (such as the number of months in a year) and readonly for values fixed when an object is created or that might change in a future version.
Naming: some programmers write constants as DAYS_IN_YEAR to set them apart from ordinary variables. Current C# style uses PascalCase (DaysInYear) for constants.
Classroom application
- Design a
Studentclass with properties for name (string), GPA (double), and serial number (long). - Create two students with target-typed
new()and object initializers, change one, and print both to show they are separate. - Assign one student reference to another variable, change the name through the second variable, and predict the output of the first.
- Set a reference to
null, try to print a property, and read the resulting exception. - Add an
Addressclass with street, city, and state properties and giveStudentanAddressproperty; create a student with nested initializers and printstudent.Address.City. - Add
public static int studentCount;and increment it after each student is created. Add aconst double MaxGpa = 4.0;.
Common misconceptions
- Defining a class does not create an object;
newdoes. - A class variable holds a reference to an object, not the object itself.
- Assigning one reference to another shares the object; it does not copy it.
- Separate
newcalls create separate objects that do not affect each other. - Using a
nullreference throws an error at run time. - Public fields and properties are not the same; properties control access.
- Target-typed
new()works only when the declared type is known. - Static members belong to the class and are reached through the class name.
constmust be initialized in its declaration and is automatically static;readonlycan be set in a constructor.- An object is garbage-collected only after every reference to it is gone.
Lesson summary
A class bundles unlike data and the code that works on it into one type that models a real-world concept. OOP rests on abstraction, encapsulation, reuse, inheritance, and polymorphism, and access modifiers protect a class’s internals. A class declares properties (preferred over public fields), methods, and events; new instantiates an object, and the dot operator reaches its members. Object initializers with target-typed new() make creation clear, even for classes that contain other classes. Class variables hold references, so several variables can share one object, and null means no object. Static members belong to the class, instance members to each object, and const and readonly protect values from change. Lesson 12 focuses on methods.
Course Notes