3. Methods
Chapter 0011 2
What’s the Point?
-
Create
void
methods and methods that return a value -
Use parameters to pass data to methods
-
Use
return
statements to return values from methods -
Create overloaded methods
Source code examples from this chapter and associated videos are available on GitHub.
As our programs begin to get larger and more complex, it will be important to keep our code organized. Each program we write is contained within a class, and classes are the basic building block of an OOP program; we’ll explore them in more detail in Classes and Objects. Within a class or program, we can organize code in methods. A method is a collection of statements that work together to complete a single task. Consider the assembly instructions for building a LEGO set.

It might take dozens of small steps to complete the set, but taken together, the instructions execute a single task: building the set. A method is conceptually the same.
If you’ve worked with other programming languages, you may know methods by a different name. In Python, we use functions, and in other languages we use procedures or subroutines. All of those terms pretty much mean the same thing, but in C# they’re called methods. (And really, they’re generally called methods whenever we’re using object-oriented programming, so even in Python we sometimes call them methods.)
There are a variety of reasons to break our code into methods, but an important advantage for now is reusability. Once we create a method, we can use it as often as we’d like. That means we don’t have to type the same code over and over. In turn, that improves our code’s maintainability. If we have to perform some calculation ten times in our program and have written out that calculation all ten times, a change to the calculation means updating it in all ten places. But if we put that calculation in a method and use that method ten times, we can just update the method and our changes will automatically be used all ten times. We’ll see additional advantages to methods as we learn more about programming.
Once we define a method, we invoke the method each time we want it to execute. We also say that we call a method, which sounds a little cooler than invoke but means the same thing.
3.1. Defining Our Own Methods
We’ve been defining a method from the start—or, more likely, the compiler has been defining one for us.
The .NET runtime—which takes the compiler version of our code and executes it—looks for a method called Main
, and that’s where it starts running the code.
If we’re using top-level statements, the compiler creates that Main
method for us; if we’re using the traditional approach, we’ve defined Main
in our code.
We’ve also been calling (invoking) methods from the start.
To output text, we’ve been calling the WriteLine()
and Write()
methods.
In C#, parentheses always indicate a method, whether or not there is anything inside of the parentheses. Anytime you see parentheses in C# code, you’re looking at either a method definition or a method call. |
A method definition includes a method header followed by a code block.
1
2
3
4
public void DisplayBandName()
{
Console.WriteLine("The Temptations");
}
-
The
public
keyword is an access modifier. Defining the method aspublic
makes it possible to call the method from anywhere. More on this later, but for now all of our methods will bepublic
. -
The
static
keyword basically means that the method belongs to the class and not an object. That will make more sense in the next chapter, but for now our methods will bestatic
. -
The
void
keyword indicates that this method will not return anything. This is called the method’s return type, and we’ll learn about this shortly. -
DisplayBandName
is the identifier for the method, in the same way variables have identifiers. Like variable identifiers, new words within the name get captialized; however, method names also have a capital first letter. This naming style is called Pascal case (while variables use camel case). And as with variables, make the name descriptive. The convention is that the name of a method should describe what it does; usually, that means the identifier is (or includes) a verb, like "display" in this example. -
The parentheses indicate to the compiler that this is a method, as opposed to a variable or some other fancy thing. You’ll see what we can put in the parentheses shortly.
3.2. Variable Scope
As we begin organizing our code into different methods and (when we learn object-oriented programming basics) classes, we’ll need to understand how data is compartmentalized within a program. Whenever we create a class or method—and other structures we’ll learn as we go—we use curly braces to define the boundaries and indenting to help make those boundaries clear. These braces form code blocks.
The outermost code block is our class—again, that’s created by the compiler if we’re using top-level statements. Although there are a few things that can go outside the class, like when we need to import outside code to use, the class code block contains all of the components of our program.
In some cases we can place one block inside another, as such as putting a method inside a class. This is called nesting blocks, and a nested block must be completely enclosed; in other words, a method can’t be partly in a class and partly out of it.
And some kinds of blocks can’t be nested. A method can be nested inside a class, but a method cannot be nested inside another method. Many IDEs, including Visual Studio, Visual Studio Code, and JetBrains Rider use color coding to make code blocks more clear.

A variable can only be used or accessed inside the block in which it was declared, or within a block nested within that block; this is the variable’s scope
.
When we refer to a variable, the compiler checks within that code block, or scope, to see if the variable has been declared.
If it doesn’t find a variable with that identifier within the current scope, it will move out to the enclosing code block (if it is nested within a class, for example) and check there.
If the variable is still not found, the compiler stops and produces an error.
Basically, referring to a variable that is declared in a different scope is the same as referring to a variable we never declared at all. Trying to use a variable in a different code block is referred to as an out of scope reference.
public class ScopeExample
{
public static void Main()
{
int favoriteNumber = 7;
Console.WriteLine(favoriteNumber); (1)
OutputNumber();
}
public static void OutputNumber()
{
Console.WriteLine(favoriteNumber); (2)
}
}
1 | This is a valid, or in scope, reference because favoriteNumber is declared within Main() . |
2 | This is an invalid out of scope reference because favoriteNumber can only be accessed within Main() . |
3.2.1. Variable Shadowing
When we first started using variables, we learned that we can’t make two variables with the same name, but it’s a little more nuanced than that. We can’t make two variables with the same name and scope. Java will allow us to declare a variable with the same name in a different scope, which is called variable shadowing. Shadowing is a very bad practice, because it often leads to confusion about which variable is in scope (though we’ll see an exception down the road).
The example below can be confusing to beginners and to people who are reading the code quickly.
When OutputNumber()
is called, another variable named favoriteNumber
is created and assigned the value 18
.
After that is output, an assignment statement changes that value to 10
.
Then, program execution returns to Main()
, where a WriteLine()
statement outputs favoriteNumber
again.
However, this favoriteNumber
wasn’t changed to 10—the other one was.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ShadowingExample
{
public static void Main()
{
int favoriteNumber = 7;
Console.WriteLine(favoriteNumber); (1)
OutputNumber();
Console.WriteLine(favoriteNumber); (2)
}
public static void OutputNumber()
{
int favoriteNumber = 18;
Console.WriteLine(favoriteNumber); (3)
favoriteNumber = 10;
}
}
1 | This outputs 7 |
2 | This outputs 18 , because it refers to the variable declared in outputNumber() |
3 | This still outputs 7 because the change to 10 is made to the favoriteNumber within the outputNumber() method. |
3.2.2. Global Variables
As you can see, variable scope has a big impact on how our code runs. Beginning programmers sometimes try to avoid scope issues by declaring their variables within the class code block, which makes them accessible to any block nested within the class—inclusing all of the methods the class encloses. This kind of class-level variable is sometimes called a global variable, and the use of global variables is generally discouraged.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class GlobalVariableExample
{
static int favoriteNumber = 7; (1)
public static void Main()
{
Console.WriteLine(favoriteNumber);
OutputNumber();
Console.WriteLine(favoriteNumber);
}
public static void OutputNumber()
{
Console.WriteLine(favoriteNumber);
favoriteNumber = 18; (2)
}
}
1 | Declaration at the class level. Note that global variables must be static . |
2 | This changes the value of favoriteNumber to 18 for all methods in the program. |
Instead, we’ll declare all of our variables within our methods; these are called local variables.
The use of global or class-level variables in code that you turn in for an assignment in my class is very heavily penalized. As much as possible, I try to reinforce best practices—and that means minimizing the use of global variables. |
Of course, this presents a problem. What if we need access to a variable in another method? The best practice is to pass that variable value to the method as needed, and for the method to pass back a value when necessary.
In the chapter on classes and objects, we will start using variables that look a lot like the global variables I just said we shouldn’t use. To be clear, those instance variables behave differently and serve a different purpose. They are global variables as described here. |
3.3. Passing Data to Methods
Sometimes a method needs some information in order to carry out its purpose. In our LEGO analogy, we need to have the right pieces to create the completed set; we have to hand over those pieces so they can be used to carry out that task.
We’ve been using methods that similarly need information to carry out their task.
For example, the Write()
method needs to know what it’s supposed to print.
To provide information to a method, we pass the information in as arguments.
So, the string
we want to output is passed to the Write()
method as an argument, and arguments are always placed inside the parentheses:
1
Console.Write("Hello World");
In this example, "Hello World" is an argument.
We establish what information a method needs as part of the method definition. Within the method we’re defining, those pieces of information are called parameters. A parameter is a variable that exists in the method and receives the argument, and it’s declared inside the parentheses in our method definition. The methods we’ve defined so far didn’t need any outside information, so we haven’t been putting anything in the parentheses—but now let’s see an example with a parameter.
public class ParameterExample
{
public static void Main()
{
OutputGreeting("Tim"); (1)
}
public static void OutputGreeting(string name)
{ (2)
Console.WriteLine("Hello, " + name + "!");
}
}
1 | "Tim" is the argument. |
2 | name is the parameter. |
In the above example, "Tim" is passed to the OutputGreeting()
method as an argument.
Within that method, the parameter name
stores the argument, so when this code runs, name
is equal to "Tim".
The actual value passed in when we call a method is referred to as an argument. The variable that receives that value within the method is referred to as a parameter. Though some people use the terms interchangeably, I’ll keep them separate. |
We can use as many or as few parameters as we need. In general, think about what information the method needs to do its job, and then add parameters for those necessary pieces of information.
Take It for a Spin
This is a good time to stop and try out what you’ve learned so far. The Coding Practice assignment for this chapter can be completed with the concepts covered to this point.
Deep Cut
If you specify paramenter names in a method call, the argument order doesn’t matter.
Deep Cut
You can define some parameters as optional, which means you don’t have to provide an argument for them.
3.4. Returning Values
The methods we’ve seen to this point are basically commands—they simply perform a task, and then when they’re done, program execution just goes back to the method that called it and resumes there. But we can also create methods that are like questions—they execute a chunk of code, but when they are finished they give back an answer.
In our LEGO analogy, the method would accept the pieces and give back the completed set.
Consider the two methods below.
1
2
3
4
5
6
7
8
9
10
11
public static void OutputFavNum()
{ (1)
int favNum = 10 - 3;
Console.Write(favNum);
}
public static int GetFavNum()
{ (2)
int favNum = 10 - 3;
return favNum;
}
1 | This specifies a return type of void |
2 | This specifies a return type of int |
The first method is a command to print out a favorite number, so it does not return anything.
The void
in the method header is the return type, and void basically means nothing; this method returns nothing.
The second method is asking to get a favorite number, so it is going to give back an int
. The return type is specified as int
.
The return
statement sends the favNum
value back to the method that called GetFavNum()
.
If a method has a return type of anything other than void , it will only compile if it has a return statement followed by a value—either literal or variable—of the specified type.
|
This means that methods themselves essentially have data types.
OutputFavNum()
has a data type of void
.
GetFavNum()
has a data type of int
.
Since methods have types, we can use them in statements just as we’d use a literal or variable of that type.
For example, the following line of code is valid:
int answer = 18 + GetFavNum();
This evaluated the same way as any other assignment statement. It starts on the right and finds that method call, so it will execute getFavNum()
and plug the returned value into the operation, resulting in 18 + 7
and ultimately evaluating to 25
, which is then assigned to answer
.
A return
statement in a void
method stops execution of the method and returns to the calling method.
return
statement before the end of a method.1
2
3
4
5
6
public static void ShortCircuit()
{
Console.Write(“This line of code runs...”);
return;
Console.Write(“This can never run!”);
}
The second Write() statement won’t execute because the return statement ends the method. The compiler doesn’t like these kinds of unreachable statements, though, so it will not compile.
return
statements in void
methods will have some uses for us later on.
C# offer a clever thing called a tuple that lets us return multiple values from one method. But that’s beyond what we’ll be doing, so as far as we’re concerned a method can only return one piece of data, and it can only have one return type. |
3.4.1. Returning vs. Outputting
Generally speaking, it’s better to return values from a method rather than outputting values.
There are a few reasons for this, but consider an obvious one.
If we use a Write()
method, our code is limited to only working in a console application.
That’s fine for now, because it’s the only kind of application we know how to make!
But what if we want to use that same code in a web application, or a mobile app?
That Write()
statement won’t work—rather, the user will never see the result, because they won’t have a console window.
Consider this code:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class BadOutput
{
public static void Main()
{
KingOfSoul();
}
public static void KingOfSoul()
{
Console.WriteLine("Sam Cooke");
}
}
If the KingOfSoul()
method knows who the King of Soul is, how do we print that out if we can’t perform the output inside the method?
The solution is to return the string and perform the output in Main()
.
Main()
to be output. 1
2
3
4
5
6
7
8
9
10
11
12
13
public class GoodOutput
{
public static void Main()
{
Console.WriteLine(KingOfSoul());
}
public static string KingOfSoul()
{
return "Sam Cooke";
}
}
This is another example of something that seems annoying, like it’s just extra work. When we’re learning new things, we sometimes have to accept the wisdom of people who are experienced—and recognize that eventually we’ll see the point. We’re all about learning good habits and best practices around here, so we’ll almost always return values rather than printing them.
There are times when we want a method whose sole purpose is to produce some output. In that case, we should give it an appropriate identifier. Notice that those kinds of methods in my examples have print or output in the name.
On assignments, I rarely want students to create methods that produce output, and when I do I always make that explicit in my directions.
When I refer to returning something, I mean just that.
The rule of thumb is, all input and output statements should be in the Main()
method and data should be passed around as necessary.
I strongly penalize input and output statements outside of the Main() method because I’m trying to build habits that will serve you well as you learn more about programming.
|
The Lab Assignments in Canvas can be completed using what we’ve covered to this point. You might choose to complete that work now, then move onto the next section—which you’ll need for the Programming Project. |
3.5. Overloading a Method
Sometimes the task, action, or calculation that a method produces has different ways of operating depending on the circumstances.
Consider a method that calculates the average age of two people:
AverageAge()
method, which calculates the average of two ages.1
2
3
4
public static double AverageAge(int age1, int age2)
{
return (double) (age1 + age2) / 2;
}
This is a pretty straightforward method.
Notice that the return statement uses casting with (double)
to ensure that the result is not truncated.
If we want to calculate an average age of 3 people, we could almost use the same method. We want a method that still calculates the average age, but takes three arguments and divides by 3, instead of 2.
To create another version of a method that operates a little differently, we can use method overloading.
To overload a method, we write a new method with the same identifier, but with a different set of parameters.
An overload for our averageAge()
method could look like this:
AverageAge()
to calculate the average of three ages.1
2
3
4
public static double AverageAge(int age1, int age2, int age3)
{
return (double) (age1 + age2 + age3) / 3;
}
Note that the method identifier is exactly the same, but this version accepts three int
arguments instead of two.
That difference allows the compiler to easily determine which implementation of the method is being called: if there are two `int`s in the parenthesis, it calls the first implementation, and if there are three `int`s, it calls the second implementation.
When we’re using the method, we can call whichever best suits our needs at the time.
The compiler can also easily distinguish overloaded methods if the types of the parameters are different. An implementation that accepts `double`s is valid:
AverageAge()
to calculate the average of three ages, using double
values.1
2
3
4
public static double AverageAge(double age1, double age2, double age3)
{
return (double) (age1 + age2 + age3) / 3;
}
If the compiler sees a call to AverageAge()
with three double
values, it will invoke this last version.
3.5.1. Incorrect Overloading
Overloaded methods must have differences in the number and/or types of the parameters. The names of those parameters doesn’t differentiate them, so different names is not enough to make a valid overload.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BadOverloading
{
public static void Main()
{
Console.WriteLine(area(15, 10)); (1)
}
// Calculates area of a rectangle
public static double Area(double length, double width)
{
return length * width;
}
// Calculates area of an oval
public static double Area(double smallRadius,
double bigRadius)
{
double area = 3.14 * smallRadius * bigRadius;
return area;
}
}
1 | The compiler can’t tell if we want the area implementation of a rectangle of the implementation for an oval. |
The term we use to describe a method’s identifier and parameter list (the number, order and types of parameters) is a method signature. The method signature must be unique so the compiler can identify which method code to run.
AverageAge()
.public class AverageAge
{
public static void Main()
{
Console.WriteLine(averageAge(1.25, 1.5, .5)); (1)
Console.WriteLine(averageAge(10, 20)); (2)
Console.WriteLine(averageAge(10, 20, 25)); (3)
}
public static double AverageAge(int age1, int age2)
{
return (double) (age1 + age2) / 2;
}
public static double AverageAge(int age1, int age2, int age3)
{
return (double) (age1 + age2 + age3) / 3;
}
public static double AverageAge(double age1, double age2, double age3)
{
return (double) (age1 + age2 + age3) / 3;
}
}
1 | The compiler sees three double values, so it calls the third implementation. |
2 | The compiler sees two int values, so it calls the first implementation. |
3 | The compiler sees three int values, so it calls the second implementation. |
3.6. Solution Walkthrough
In "solution walkthrough" videos, I give a problem/prompt that is similar to the kinds of work I assign, and then I record myself writing a solution. It’s not absolutely mandatory to watch this video, but students report that these videos are particularly helpful.
Check Yourself Before You Wreck Yourself (on the assignments)
Chapter Review Questions
Sample answers provided in the Liner Notes.