Chapter 16: The Class Blueprint - Defining Custom Types
Theoretical Foundations
A class is a blueprint for creating objects. Think of it like the architectural plans for a house. The blueprint itself isn't a house; it’s the definition of what a house looks like, what rooms it has, and what it can do. Once you have the blueprint, you can build many actual houses (objects) based on that design.
In C#, classes allow us to group data and the actions that operate on that data into a single unit. This is the foundation of Object-Oriented Programming (OOP). In previous chapters, we learned how to create variables (like int age) and methods (like CalculateScore) separately. Now, we bring them together inside a class.
The House Blueprint Analogy
Imagine you are building a video game. You need to create "Player" characters.
- The Class (
Player): This is the blueprint. It says every player has aname(data) and canAttack(action). - The Object (
myPlayer): This is the actual player created from the blueprint. It has a specific name, "Arthur", and its own health points.
You can create thousands of players (objects) from the single Player class blueprint.
1. Defining a Class
To define a class, we use the class keyword followed by the class name. The definition is enclosed in curly braces { }.
Syntax:
Let's create a blueprint for a Robot in our game.
using System;
class Robot
{
// 1. Fields (Data)
// These are variables that belong to the class. They store the state.
// We use access modifiers like 'private' to protect data (Encapsulation).
private string model;
private int batteryLevel;
// 2. Constructor (Initialization)
// This special method runs when we create a new Robot.
// It sets up the initial state.
public Robot(string initialModel, int initialBattery)
{
model = initialModel;
batteryLevel = initialBattery;
}
// 3. Methods (Behavior)
// These define what the Robot can do.
public void DisplayStatus()
{
// We access the fields using 'this' to refer to the current object
Console.WriteLine($"Model: {model}, Battery: {batteryLevel}%");
}
public void ConsumeEnergy(int amount)
{
// We can modify the fields
batteryLevel = batteryLevel - amount;
// Logic from Chapter 6: if statements
if (batteryLevel < 0)
{
batteryLevel = 0;
}
}
}
2. The Core Components of a Class
A. Fields (The Data)
Fields are variables declared inside a class. They hold the data specific to an object.
- Analogy: The number of windows and doors in a specific house.
- Concept: In Chapter 15, we learned about Scope and the Stack vs. Heap. When we create an object, its fields live on the Heap (dynamic memory), while local variables inside methods live on the Stack.
- Encapsulation: We usually mark fields as
private. This means other parts of the program cannot change them directly. This protects the data from accidental corruption.
B. Constructors (The Setup)
A constructor is a special method that has the same name as the class and no return type. It is called automatically when you use the new keyword.
- Analogy: The construction crew finishing the house so it's ready to move in.
- Purpose: It ensures that an object starts in a valid state. For example, we ensure the
batteryLevelis set immediately when theRobotis created.
C. Methods (The Actions)
Methods are functions defined inside a class. They perform operations using the class's data.
- Analogy: Turning on the lights, opening the garage door.
- Concept: These work exactly like the static methods from Chapter 13, but they belong to the object instance and can access the object's fields.
3. Creating Objects (Instantiation)
Defining the class doesn't create a robot; it just describes what a robot is. To create an actual robot, we instantiate an object using the new keyword.
Step-by-Step:
- Declaration:
Robot myRobot;(I have a variable that can hold a Robot). - Instantiation:
new Robot("R2-D2", 100);(I build a Robot in memory). - Assignment:
myRobot = ...(I store the reference to that Robot in my variable).
Usually, we do this in one line:
Visualizing the Memory (Stack vs Heap):
From Chapter 15, you know that myRobot is a reference variable. It sits on the Stack and points to the actual Robot object on the Heap.
4. Access Modifiers and Encapsulation
We use access modifiers to control who can see and use the members of our class.
public: Accessible from anywhere.private: Accessible only within the class itself.
Why use private?
Imagine if any part of your code could change the batteryLevel of the robot directly.
batteryLevel private, we force other code to use our ConsumeEnergy method, which includes logic (the if statement) to ensure the battery doesn't go below 0. This is Encapsulation—bundling data with the methods that operate on that data, protecting the data from invalid states.
5. Practical Example: The AI Agent Class
In the context of building AI applications, classes are essential for managing the complexity of an AI agent. An AI agent needs to remember context (data) and have methods to process input and generate output.
Let's build a simplified AIAgent class using only concepts from Chapters 1-16.
using System;
// The Blueprint
class AIAgent
{
// Fields (Data stored on the Heap)
private string agentName;
private string systemPrompt;
private int tokensUsed; // Tracks usage (like a cost counter)
// Constructor
public AIAgent(string name, string prompt)
{
agentName = name;
systemPrompt = prompt;
tokensUsed = 0;
}
// Method: Simulates generating a response
// Returns a string (Chapter 14: Return Values)
public string GenerateResponse(string userPrompt)
{
// Calculate cost (simulated logic)
int promptLength = userPrompt.Length;
tokensUsed = tokensUsed + promptLength; // Increment (Chapter 4)
// Construct the response using String Interpolation (Chapter 3)
string fullResponse = $"[{agentName}]: Processed your request. Context: {systemPrompt}";
return fullResponse;
}
// Method: Check usage stats
public void LogUsage()
{
Console.WriteLine($"Agent {agentName} has used {tokensUsed} tokens.");
}
}
// Main Program to use the class
class Program
{
static void Main()
{
// 1. Instantiate the AI Agent
// We create a specific instance named 'ChatBot'
AIAgent chatBot = new AIAgent("ChatBot", "You are a helpful assistant.");
// 2. Interact with the object
// We call methods on the specific instance
string response1 = chatBot.GenerateResponse("Hello!");
Console.WriteLine(response1);
string response2 = chatBot.GenerateResponse("What is the weather?");
Console.WriteLine(response2);
// 3. Check the state (Encapsulation in action)
// The 'tokensUsed' field is private, so we use the public method to see it
chatBot.LogUsage();
}
}
Output:
[ChatBot]: Processed your request. Context: You are a helpful assistant.
[ChatBot]: Processed your request. Context: You are a helpful assistant.
Agent ChatBot has used 36 tokens.
6. Why Classes Matter for AI Development
In AI development, you rarely write a single script that runs once. You build systems that manage state over time.
- State Management: An AI model doesn't remember your conversation unless you provide that memory. By creating a class like
AIAgent, we create a place to store thetokensUsedor theconversationHistory. Without a class, this data would be lost or require messy global variables. - Modularity: You can create multiple agents easily. In the example above, we could create
AIAgent coder = new AIAgent("CodeHelper", "You write C# code");andAIAgent writer = new AIAgent("StoryTeller", "You write fiction");. They operate independently but use the same logic. - Abstraction: When you use an AI library (like OpenAI or Local Llama), you don't need to know how the neural network works. You interact with a
Clientclass. You callclient.GenerateText(prompt). The class hides the complex HTTP requests and JSON parsing inside its methods.
7. Key Takeaways
- Blueprint vs. Building: A class is the definition; an object (instance) is the concrete realization.
newKeyword: This allocates memory on the Heap and returns a reference.- Encapsulation: Use
privatefields andpublicmethods to protect your data integrity. thisKeyword: Inside a class method,thisrefers to the current object instance (e.g.,this.modelvs a local variablemodel).- No
var: As per our constraints, we explicitly declare types (Robot myRobot = new Robot(...)).
By mastering the class blueprint, you move from writing procedural scripts (lists of instructions) to building structured, reusable systems—the foundation of any robust AI application.
Basic Code Example
Here is a basic code example introducing the concept of classes.
The Scenario: Modeling a Real-World Object
In the physical world, we interact with distinct objects. A car, for example, has specific attributes (like its color or current speed) and can perform actions (like accelerating or braking). In programming, a class acts as a blueprint for these objects. It defines what data an object holds and what it can do.
In this example, we will create a blueprint for a SimpleBankAccount. We will then use that blueprint to create an actual bank account object, deposit money, and check the balance.
The Code Example
using System;
namespace Chapter16_BasicExample
{
// 1. The Class Blueprint
// This defines what a "SimpleBankAccount" looks like.
// It is not an account itself; it is the template for creating accounts.
public class SimpleBankAccount
{
// 2. Fields (Data Storage)
// These variables hold the state of the object.
// We use 'double' to allow for decimal values (e.g., 100.50).
// We mark them 'private' so they can only be changed through specific methods.
private double _balance;
private string _accountHolderName;
// 3. Constructor
// This special method runs automatically when we create a new instance.
// It sets the initial values for the fields.
public SimpleBankAccount(string name, double startingBalance)
{
_accountHolderName = name;
_balance = startingBalance;
}
// 4. Methods (Behavior)
// These define what the object can do.
// A method to add money to the account.
public void Deposit(double amount)
{
// We use simple arithmetic to update the field.
_balance = _balance + amount;
Console.WriteLine($"Deposited: {amount}. New Balance: {_balance}");
}
// A method to check the current balance.
public void DisplayBalance()
{
Console.WriteLine($"Account Holder: {_accountHolderName}");
Console.WriteLine($"Current Balance: {_balance}");
}
}
class Program
{
static void Main(string[] args)
{
// 5. Instantiation (Creating an Object)
// We use the 'new' keyword to create an actual instance (object)
// based on the SimpleBankAccount blueprint.
// We pass arguments to the constructor to initialize the object.
SimpleBankAccount myAccount = new SimpleBankAccount("Alice", 1000.00);
// 6. Using the Object
// We call methods on the specific object 'myAccount'.
// Check initial balance
myAccount.DisplayBalance();
Console.WriteLine(); // Adds a blank line for readability
// Perform an action (Deposit)
myAccount.Deposit(500.00);
Console.WriteLine();
// Check balance again to see the change
myAccount.DisplayBalance();
}
}
}
Step-by-Step Explanation
-
Defining the Blueprint (
class SimpleBankAccount): We start by declaring a class using theclasskeyword. This creates a new custom type. Inside the curly braces{ }, we define the internal structure of this type. -
Storing Data (Fields): We declare two variables inside the class:
_balanceand_accountHolderName.- Why fields? These represent the "memory" of the object. Unlike local variables inside a method (which disappear once the method finishes), fields exist as long as the object exists.
- Why
private? We restrict access using theprivateaccess modifier. This is a core concept of encapsulation. We don't want external code to arbitrarily change the balance to a negative number. We will control changes through methods.
-
Initializing the Object (Constructor): The
public SimpleBankAccount(string name, double startingBalance)block is a constructor.- It has the same name as the class and no return type.
- When we create an object later using
new, this constructor runs immediately. - It takes parameters (
nameandstartingBalance) and assigns them to the object's internal fields (_accountHolderNameand_balance).
-
Defining Behavior (Methods): We define two methods:
DepositandDisplayBalance.Deposit(double amount): This method accepts a parameter and updates the_balancefield using arithmetic (_balance = _balance + amount).DisplayBalance(): This method reads the fields and prints them to the console usingConsole.WriteLine.
-
Creating the Object (Instantiation): Inside the
Mainmethod, we create the actual object:SimpleBankAccountis the type (the blueprint).myAccountis the variable name (the reference to the object).newis the keyword that allocates memory for the object (on the Heap, as discussed in Chapter 15)."Alice"and1000.00are passed to the constructor to initialize the specific data for this account.
-
Interacting with the Object: We use "dot notation" (
.) to access the members of the object.myAccount.DisplayBalance()calls the method specific tomyAccount.- If we created a second object,
SimpleBankAccount bob = new SimpleBankAccount(...), callingbob.DisplayBalance()would show Bob's data, completely separate from Alice's.
Visualizing the Relationship
The following diagram illustrates how the blueprint (Class) relates to the actual objects (Instances).
Common Pitfalls
1. Forgetting the new Keyword
A common mistake is declaring a variable of a class type but failing to instantiate it.
new, it points to nothing (null). Calling a method on null causes the program to crash. You must always use new to create the actual object in memory before using it.
2. Confusing Fields and Local Variables
Inside the Deposit method, we used _balance (the field). If we declared a local variable named balance inside the method, it would "shadow" the field, meaning the field would not update.
public void Deposit(double amount)
{
double balance = 0; // This is a NEW local variable, not the object's field.
balance += amount; // This changes the local variable, which is immediately lost.
}
_ to distinguish it) if you want to persist data within the object.
The chapter continues with advanced code, exercises and solutions with analysis, you can find them on the ebook on Leanpub.com or Amazon
Code License: All code examples are released under the MIT License. Github repo.
Content Copyright: Copyright © 2026 Edgar Milvus | Privacy & Cookie Policy. All rights reserved.
All textual explanations, original diagrams, and illustrations are the intellectual property of the author. To support the maintenance of this site via AdSense, please read this content exclusively online. Copying, redistribution, or reproduction is strictly prohibited.