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
- What a method is and why it lives inside the class
- Method syntax: parameters and return type
voidmethods: actions with no return value- Adding methods to
Book:Lend(),Return(),ShowDetails() - Method overloading
- Passing parameters by value
- Passing parameters by reference:
refandout - Optional parameters and named arguments
- Instance methods versus
staticmethods
- 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.
- Method syntax: parameters and return type
The general form of a method is:
- 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 usepublicso 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...), orvoidif 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).
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
void methods: actions with no return value
void methods: actions with no return valueWhen 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.
- Adding methods to
Book: Lend(), Return(), ShowDetails()
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.
- 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 ISBNC# 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 |
- 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 5Even 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 typeBookis 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 aBookas 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.
- Passing parameters by reference:
ref and out
ref and outWhen 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 changeout: 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); // Trueref |
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) |
- 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 valueNamed 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.
- Instance methods versus
static methods
static methodsAll 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
}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/outwhen calling the method: if the method was declared withreforout, 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
outvariable without initializing it and expecting to read it before the call: a variable declared asout 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
TitleorAvailablefrom astaticmethod: astaticmethod 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 (likeTryParse/TryLend), but a method that returns its result withreturnis usually clearer than one that does so viaref/outparameters; save them for when they genuinely add value, not out of habit.
Exercises
-
Add to the
Bookclass (with the propertiesTitle,Author,Isbn,Available) an instance methodbool IsByAuthor(string targetAuthor)that returnstrueif the book'sAuthorexactly matchestargetAuthor, andfalseotherwise. Test it with aBookobject and two different searches. -
Write a
staticmethodbool IsValidIsbn(string isbn)inside theBookclass that returnstrueif, after removing the hyphens withReplace("-", ""), the resulting string has exactly 13 characters, andfalseotherwise. Call it withBook.IsValidIsbn(...)using two example ISBNs (one valid and one not). -
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 specifyingloanDays(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.
C# Programming Course
Module 1: Introduction to C#
- Introduction to C#
- Setting Up the Development Environment
- Hello World Program
- Basic Syntax and Structure
- Variables and Data Types
- Arrays and Strings
Module 2: Control Structures
Module 3: Object-Oriented Programming
- Classes and Objects
- Methods
- Constructors and Destructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- Structs and Records: Value Types and Reference Types
Module 4: Advanced C# Concepts
- Interfaces
- Delegates and Events
- Pattern Matching and Modern C# Features
- Generics
- Collections
- LINQ (Language Integrated Query)
- Asynchronous Programming
Module 5: Working with Data
- File I/O
- Serialization
- Database Connectivity
- Entity Framework
- Working with JSON and Consuming REST APIs
Module 6: Advanced Topics
- Reflection
- Attributes
- Dynamic Programming
- Memory Management and Garbage Collection
- Multithreading and Parallel Programming
Module 7: Building Applications
Module 8: Best Practices and Design Patterns
- Coding Standards and Best Practices
- Design Patterns
- Dependency Injection and Inversion of Control
- Unit Testing
- Code Review and Refactoring
