In the previous lesson you defined the Book class with its properties (Title, Author, Isbn, Available), but a Book object was, for now, a passive data container: lending it required writing external code that set Available to false by hand, and showing its details required writing the corresponding Console.WriteLine every time, anywhere in the program that needed it. This lesson fixes that by teaching you to define methods: blocks of code associated with a class that represent the actions its objects can perform. By the end of the lesson, a Book object will know how to lend itself, be returned, and show its own details, without the code that uses it needing to know the internal details of how it does so.

Contents

  1. What a method is and why it lives inside the class
  2. Method syntax: parameters and return type
  3. void methods: actions with no return value
  4. Adding methods to Book: Lend(), Return(), ShowDetails()
  5. Method overloading
  6. Passing parameters by value
  7. Passing parameters by reference: ref and out
  8. Optional parameters and named arguments
  9. Instance methods versus static methods

  1. What a method is and why it lives inside the class

A method is a function defined inside a class that describes an action its objects can perform. The difference from the local functions you already used in Module 2 (such as LendBook(string title), which received the title as a parameter) is that a method lives inside the class and, normally, operates directly on the properties of the object itself, without needing them to be passed in as parameters.

class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public string Isbn { get; set; }
    public bool Available { get; set; }

    public void Lend()
    {
        Available = false;
    }
}

Notice the difference in approach: in Module 2, LendBook(bookTitles, availability, position) needed you to pass the entire availability array as a parameter. Now, book1.Lend() doesn't need any parameter: the method already knows which object it belongs to (book1) and accesses its Available property directly. This ability of a method to access the properties of the object it's invoked on is the foundation of object-oriented programming.

  1. Method syntax: parameters and return type

The general form of a method is:

<access modifier> <return type> <MethodName>(<parameters>)
{
    // method body
}
  • The access modifier (public, private...) controls where the method can be called from; it's studied in depth in the Encapsulation lesson. For now, we'll use public so that it's accessible from anywhere in the program.
  • The return type indicates what type of data the method returns when it finishes (int, string, bool...), or void if it doesn't return any value.
  • The parameters, in parentheses, are the data the method needs to receive to do its job (there can be zero, one, or several).
public bool HasAuthor(string targetAuthor)
{
    return Author == targetAuthor;
}

This method returns a bool: true if the book's Author matches the targetAuthor parameter, false otherwise. The return keyword ends the method's execution and hands the given value back to the caller.

Book book1 = new Book { Title = "Hopscotch", Author = "Julio Cortazar" };
bool isByCortazar = book1.HasAuthor("Julio Cortazar");
Console.WriteLine(isByCortazar); // True

  1. void methods: actions with no return value

When a method performs an action but doesn't need to return any data, its return type is void (literally, "empty"). You already used void in the local functions of Module 2; in a class method it behaves the same way:

public void ShowDetails()
{
    Console.WriteLine($"Title: {Title}");
    Console.WriteLine($"Author: {Author}");
    Console.WriteLine($"ISBN: {Isbn}");
    Console.WriteLine($"Available: {Available}");
}

A void method can use return; (with no value after it) to end early if needed, but it cannot use return value;, since it has no type to return that value as.

  1. Adding methods to Book: Lend(), Return(), ShowDetails()

With what you've seen so far, you can now complete the Book class with its three main methods, which will stay with the object for the rest of the module:

class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public string Isbn { get; set; }
    public bool Available { get; set; }

    public void Lend()
    {
        if (Available)
        {
            Available = false;
            Console.WriteLine($"'{Title}' has been lent out.");
        }
        else
        {
            Console.WriteLine($"'{Title}' is not available for loan.");
        }
    }

    public void Return()
    {
        Available = true;
        Console.WriteLine($"'{Title}' has been returned.");
    }

    public void ShowDetails()
    {
        Console.WriteLine($"Title: {Title}");
        Console.WriteLine($"Author: {Author}");
        Console.WriteLine($"ISBN: {Isbn}");
        Console.WriteLine($"Available: {Available}");
    }
}

And here's how they're used on a specific object:

Book book1 = new Book { Title = "One Hundred Years of Solitude", Author = "Gabriel Garcia Marquez", Isbn = "978-84-376-0494-7", Available = true };

book1.ShowDetails();
book1.Lend();  // 'One Hundred Years of Solitude' has been lent out.
book1.Lend();  // 'One Hundred Years of Solitude' is not available for loan.
book1.Return(); // 'One Hundred Years of Solitude' has been returned.

Notice the difference from Module 2: book1.Lend() doesn't need you to pass the title, the availability array, or any position; the object itself already knows its own data and decides, based on its internal state (Available), what to do. This is the concrete improvement that object orientation brings over the loose variables of Module 2.

  1. Method overloading

Overloading consists of defining several methods with the same name but a different parameter list (different number of parameters, or different types). C# automatically decides which version to run based on the arguments it's called with.

public void ShowDetails()
{
    Console.WriteLine($"Title: {Title}");
    Console.WriteLine($"Author: {Author}");
    Console.WriteLine($"ISBN: {Isbn}");
    Console.WriteLine($"Available: {Available}");
}

public void ShowDetails(bool includeIsbn)
{
    Console.WriteLine($"Title: {Title}");
    Console.WriteLine($"Author: {Author}");

    if (includeIsbn)
    {
        Console.WriteLine($"ISBN: {Isbn}");
    }

    Console.WriteLine($"Available: {Available}");
}
book1.ShowDetails();       // uses the version with no parameters
book1.ShowDetails(false);  // uses the version with a bool, without showing the ISBN

C# distinguishes these two versions solely by their signature (name + number and types of parameters), not by the return type or by parameter names. Two methods with the same name and exactly the same parameters — even with different return types — would not be a valid overload, and the compiler would reject them.

Method Parameters When is it used?
ShowDetails() None Full details by default
ShowDetails(bool includeIsbn) One bool Lets you decide whether to show the ISBN or not

  1. Passing parameters by value

By default, in C# parameters are passed by value: the method receives a copy of the value passed to it, not the original. If the parameter is of a primitive type (int, bool, double...), modifying the parameter inside the method does not affect the original variable passed when calling it.

void Double(int number)
{
    number = number * 2;
    Console.WriteLine($"Inside the method: {number}");
}

int availableCopies = 5;
Double(availableCopies);
Console.WriteLine($"Outside the method: {availableCopies}"); // still 5

Even though inside Double the local variable number ends up as 10, the original variable availableCopies, outside the method, is unaffected: number was only a copy.

Note: this is different from what you saw in the previous lesson with objects like Book. A parameter of type Book is also passed "by value," but what gets copied is the reference to the object, not the object itself; that's why, if a method receives a Book as a parameter and modifies one of its properties (book.Available = false;), that change does show up outside the method, because the copy of the reference still points to the same object. We'll come back to this distinction between value types and reference types in the module's final lesson.

  1. Passing parameters by reference: ref and out

When you actually need a method to modify the original variable directly (not a copy), C# offers two keywords: ref and out.

ref: modifying an already-initialized variable

ref indicates that the parameter is passed by reference: the method receives direct access to the original variable, not a copy, so any change inside the method is reflected outside it. The variable must be initialized before the call, and ref must be repeated both when declaring the method and when calling it.

void DoubleCopies(ref int copyCount)
{
    copyCount = copyCount * 2;
}

int hopscotchCopies = 3;
DoubleCopies(ref hopscotchCopies);
Console.WriteLine(hopscotchCopies); // 6 -- this time it DID change

out: a method that "returns" several values

out is used so that a method can hand back an additional value besides (or instead of) its return, typically to indicate whether an operation succeeded, along with a message or result. Unlike ref, the variable passed as out does not need to be initialized before the call — the method is required to assign it a value before finishing. You already used this pattern, without knowing it yet, with int.TryParse(input, out int number) in Module 1.

bool TryLend(Book book, out string message)
{
    if (book.Available)
    {
        book.Available = false;
        message = $"'{book.Title}' lent out successfully.";
        return true;
    }

    message = $"'{book.Title}' is not available.";
    return false;
}

Book book2 = new Book { Title = "Ficciones", Available = true };

bool success = TryLend(book2, out string result);
Console.WriteLine(result); // 'Ficciones' lent out successfully.
Console.WriteLine(success);     // True
ref out
The variable must be initialized before calling Yes No
The method is required to assign it a value No Yes
Typical use Modifying an existing value Returning an additional result (often alongside a success bool)

  1. Optional parameters and named arguments

An optional parameter has a default value assigned in the method's own definition; if the caller doesn't provide that argument, the default value is used. Optional parameters must always come after the required parameters.

public void RegisterCopy(string title, string author, string condition = "New")
{
    Console.WriteLine($"Registered: {title} ({author}) - Condition: {condition}");
}
book1.RegisterCopy("Hopscotch", "Julio Cortazar");            // uses "New" by default
book1.RegisterCopy("Hopscotch", "Julio Cortazar", "Used");    // overrides the default value

Named arguments let you explicitly indicate which parameter each value corresponds to, by writing parameterName: value, instead of relying solely on order. This is especially useful when a method has several optional parameters and you only want to specify some of them:

book1.RegisterCopy(title: "Ficciones", author: "Jorge Luis Borges", condition: "Used");

// With named arguments, order stops mattering:
book1.RegisterCopy(author: "Jorge Luis Borges", title: "Ficciones");

In the second call, even though author is written before title, C# assigns each value to the correct parameter thanks to the explicit name, and condition takes its default value ("New") since it's not specified.

  1. Instance methods versus static methods

All the methods seen so far are instance methods: it only makes sense to call them on a specific object (book1.Lend()), because they operate on the data of that particular object. A static method, on the other hand, belongs to the class itself, not to any specific object, and is invoked by writing the class name instead of a variable name:

class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public string Isbn { get; set; }
    public bool Available { get; set; }

    public static bool IsValidIsbn(string isbn)
    {
        string cleanIsbn = isbn.Replace("-", "");
        return cleanIsbn.Length == 13 && cleanIsbn.StartsWith("978") || cleanIsbn.StartsWith("979");
    }

    // Lend(), Return(), ShowDetails() remain as in section 4
}
bool isValid = Book.IsValidIsbn("978-84-376-0494-7");
Console.WriteLine(isValid); // True

Notice that IsValidIsbn is invoked as Book.IsValidIsbn(...), using the class name, not on an object (book1.IsValidIsbn(...) would be a compilation error). This makes sense because validating an ISBN's format doesn't depend on any specific book: it's a general utility related to the Book concept, but one that doesn't need any object to run. For this same reason, a static method cannot directly access instance properties such as Title or Available (it wouldn't know which object to refer to); it would get a compilation error if it tried.

Instance method static method
Invoked on An object (book1.Lend()) The class (Book.IsValidIsbn(...))
Accesses object properties (Title, Available...) Yes No, not directly
Example in Book Lend(), Return(), ShowDetails() IsValidIsbn(string isbn)
When to use it The operation depends on the data of a specific object The operation is a general utility, not tied to any object

Console.WriteLine and int.Parse, which you've already used in previous modules, are also static methods (of the Console and int classes respectively): that's why they're called with the class name (Console, not a variable), and not on any object.

Common Mistakes and Tips

  • Forgetting ref/out when calling the method: if the method was declared with ref or out, the keyword must also be repeated at the call site (DoubleCopies(ref hopscotchCopies)), not just in the definition; otherwise the compiler gives an error.
  • Using an out variable without initializing it and expecting to read it before the call: a variable declared as out string message (inline, in the call itself) has no useful value until the method finishes running; don't try to read it beforehand.
  • Confusing overloading with optional parameters: both let you "call the method in several ways," but they are different mechanisms: overloading defines completely separate methods (with potentially different bodies), while an optional parameter is part of a single method with a default value.
  • Trying to access Title or Available from a static method: a static method is not tied to any object, so it can't use instance properties directly; if it needs data from a specific book, that book must be passed in as a parameter.
  • Overusing ref/out: they're useful in specific cases (like TryParse/TryLend), but a method that returns its result with return is usually clearer than one that does so via ref/out parameters; save them for when they genuinely add value, not out of habit.

Exercises

  1. Add to the Book class (with the properties Title, Author, Isbn, Available) an instance method bool IsByAuthor(string targetAuthor) that returns true if the book's Author exactly matches targetAuthor, and false otherwise. Test it with a Book object and two different searches.

  2. Write a static method bool IsValidIsbn(string isbn) inside the Book class that returns true if, after removing the hyphens with Replace("-", ""), the resulting string has exactly 13 characters, and false otherwise. Call it with Book.IsValidIsbn(...) using two example ISBNs (one valid and one not).

  3. Write a method void RegisterLoan(string memberName, int loanDays = 15) that prints a message with the member's name and the number of loan days. Call it three times: once without specifying loanDays (it should use 15 by default), another specifying 30 days by position, and another using named arguments in reversed order.

Solutions

class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
    public string Isbn { get; set; }
    public bool Available { get; set; }

    public bool IsByAuthor(string targetAuthor)
    {
        return Author == targetAuthor;
    }
}

Book book1 = new Book { Title = "Hopscotch", Author = "Julio Cortazar" };
Console.WriteLine(book1.IsByAuthor("Julio Cortazar")); // True
Console.WriteLine(book1.IsByAuthor("Jorge Luis Borges")); // False
public static bool IsValidIsbn(string isbn)
{
    string cleanIsbn = isbn.Replace("-", "");
    return cleanIsbn.Length == 13;
}

Console.WriteLine(Book.IsValidIsbn("978-84-376-0494-7")); // True (13 characters without hyphens)
Console.WriteLine(Book.IsValidIsbn("123"));                // False
void RegisterLoan(string memberName, int loanDays = 15)
{
    Console.WriteLine($"{memberName}: loan of {loanDays} days.");
}

RegisterLoan("Ana Martinez");                       // uses 15 days by default
RegisterLoan("Luis Gomez", 30);                      // 30 days by position
RegisterLoan(loanDays: 7, memberName: "Eva Ruiz");   // named arguments, reversed order

All three calls are valid: the first uses the default value of loanDays, the second specifies it by position, and the third uses named arguments, which allows writing them in any order.

Conclusion

In this lesson, Book went from being a simple data container to an object with its own behavior: it knows how to lend itself (Lend()), be returned (Return()) and show its details (ShowDetails()), and you've learned the fundamental tools for defining methods in C#: parameters and return values, void, overloading, passing by value versus ref/out, optional and named parameters, and the difference between instance methods and static methods.

There's still a pending question: how do you make sure a Book object is always born with its properties properly initialized (for example, Available = true when creating a new book), instead of relying on whoever creates it to remember to assign each property by hand? The answer is constructors, which you'll see in the next lesson along with the new Member class: the second pillar of BiblioTech's domain model.

© Copyright 2026. All rights reserved