Skip to content

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 a name (data) and can Attack (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:

class ClassName
{
    // Members (Fields, Properties, Methods, Constructors) go here
}

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 batteryLevel is set immediately when the Robot is 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:

  1. Declaration: Robot myRobot; (I have a variable that can hold a Robot).
  2. Instantiation: new Robot("R2-D2", 100); (I build a Robot in memory).
  3. Assignment: myRobot = ... (I store the reference to that Robot in my variable).

Usually, we do this in one line:

Robot myRobot = new Robot("R2-D2", 100);

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.

A reference variable named myRobot resides on the Stack, pointing to a Robot object instance stored on the Heap.
Hold "Ctrl" to enable pan & zoom

A reference variable named `myRobot` resides on the Stack, pointing to a Robot object instance stored 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.

// BAD PRACTICE (if batteryLevel was public)
myRobot.batteryLevel = -500; // This makes no sense!
By making 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.

  1. 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 the tokensUsed or the conversationHistory. Without a class, this data would be lost or require messy global variables.
  2. Modularity: You can create multiple agents easily. In the example above, we could create AIAgent coder = new AIAgent("CodeHelper", "You write C# code"); and AIAgent writer = new AIAgent("StoryTeller", "You write fiction");. They operate independently but use the same logic.
  3. 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 Client class. You call client.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.
  • new Keyword: This allocates memory on the Heap and returns a reference.
  • Encapsulation: Use private fields and public methods to protect your data integrity.
  • this Keyword: Inside a class method, this refers to the current object instance (e.g., this.model vs a local variable model).
  • 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

  1. Defining the Blueprint (class SimpleBankAccount): We start by declaring a class using the class keyword. This creates a new custom type. Inside the curly braces { }, we define the internal structure of this type.

  2. Storing Data (Fields): We declare two variables inside the class: _balance and _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 the private access 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.
  3. 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 (name and startingBalance) and assigns them to the object's internal fields (_accountHolderName and _balance).
  4. Defining Behavior (Methods): We define two methods: Deposit and DisplayBalance.

    • Deposit(double amount): This method accepts a parameter and updates the _balance field using arithmetic (_balance = _balance + amount).
    • DisplayBalance(): This method reads the fields and prints them to the console using Console.WriteLine.
  5. Creating the Object (Instantiation): Inside the Main method, we create the actual object:

    SimpleBankAccount myAccount = new SimpleBankAccount("Alice", 1000.00);
    

    • SimpleBankAccount is the type (the blueprint).
    • myAccount is the variable name (the reference to the object).
    • new is the keyword that allocates memory for the object (on the Heap, as discussed in Chapter 15).
    • "Alice" and 1000.00 are passed to the constructor to initialize the specific data for this account.
  6. Interacting with the Object: We use "dot notation" (.) to access the members of the object.

    • myAccount.DisplayBalance() calls the method specific to myAccount.
    • If we created a second object, SimpleBankAccount bob = new SimpleBankAccount(...), calling bob.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).

Diagram: G
Hold "Ctrl" to enable pan & zoom

Common Pitfalls

1. Forgetting the new Keyword A common mistake is declaring a variable of a class type but failing to instantiate it.

// Incorrect
SimpleBankAccount myAccount; 
myAccount.Deposit(100); // NullReferenceException!
In C#, a class variable is just a reference (like a pointer). Until you use 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.
}
Always use the field (often prefixed with _ 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.