11 min read

Methods

The Crash Course

Introduction

As we build larger programs, it becomes increasingly important that we have some tools to manage and organize our code. Methods are the first such tool we will learn for doing this.

Methods allow us to define a chunk of code, and associate a name with the job that it does. Once a method has been made, we can use it over and over again without copy/pasting or retyping it all.

warning

The way we’ll do methods in this tutorial is actually a bit different from how most methods are made. We will cover how to define methods in the “traditional” model (the model with the class Program and public static void Main) as well as the top-level statement model (without all that stuff). However, in a few tutorials, we’ll get into how you define classes, and we’ll see, then, a slightly altered way to define methods. That version–when we get there–is how most methods will generally be defined.

Let’s look at the following code, which counts to 10 twice:

using System;

namespace NumberMultiplier
{
    class Program
    {
        public static void Main(string[] args)
        {
            for (int number = 1; number <= 10; number++)
                Console.WriteLine(number);

            for (int number = 1; number <= 10; number++)
                Console.WriteLine(number);
        }
    }
}

info

The above code shows the “traditional” model, which includes class Program, and public static void Main, and all that other overhead. In the Hello World tutorial, we also talked about a much shorter way to do this, which is with top-level statements. In a moment, we’ll look at how this works in the top-level statement world as well.

If you look at this code, you’ll see that there are two blocks of code that are completely identical. Both for loops do the same thing.

If you’re clever, you might already be thinking that you could change this to use nested for loops. Indeed, you could, but we’re going to take this code in a different direction.

That little loop does a single job: count to ten.

A method would allow us to define this single job as its own thing, and then reuse it.

Our program already defines one method: Main.

To make a “count to ten” method, we just need to put another one in beside it.

Every method must go in something else–they don’t live on their own–and we can make our new method live inside the Program class, just like Main does. Within a class, the order of methods doesn’t matter.

So we can put our new method in either of the spots indicated by the comments in the code below:

using System;

namespace NumberMultiplier
{
    class Program
    {
        // <-- Either here...

        public static void Main(string[] args)
        {
            for (int number = 1; number <= 10; number++)
                Console.WriteLine(number);

            for (int number = 1; number <= 10; number++)
                Console.WriteLine(number);
        }

        // <-- ... or here.
    }
}

Let’s start by putting an empty method in first, to show the mechanics:

class Program
{
    public static void Main(string[] args)
    {
        for (int number = 1; number <= 10; number++)
            Console.WriteLine(number);

        for (int number = 1; number <= 10; number++)
            Console.WriteLine(number);
    }

    public static void CountToTen()
    {
    }
}

public and static are both called modifiers . We’ll get into them more later (and there are plenty of other modifiers). For now, we’ll just say that it’s the same thing the Main method has, and we’ll just keep things consistent.

note

We could have left the public off entirely, and this code would work the same, if you prefer that.

The void part is the method’s return type , which is something we’ll also ignore until a bit later in this tutorial, other than to say that this means the method doesn’t produce or return any results.

The CountToTen part is the method name. Like variable names, you can choose whatever names you want, but pick a name that helps explain what it does.

The parentheses are a part of how we define a method. We’ll do more with them in a moment.

The curly braces define a body for the method, and this is where we put statements that we want to run anytime the method is used. They’re empty right now, but we’ll fix that soon.

Overall, that is enough to define a new (empty) method!

Let’s put some code in it:

public static void CountToTen()
{
    for (int number = 1; number <= 10; number++)
        Console.WriteLine(number);
}

I’ve just copied and pasted the loop code from Main. But that’s enough to get it to count to ten anytime this method is used.

Of course, it isn’t used yet, so let’s replace the body of Main with something that calls this method:

public static void Main(string[] args)
{
    CountToTen();
    CountToTen();
}

Both of these lines do the same thing: call the CountToTen method. We’ve seen similar code when we’ve written Console.WriteLine("Hello World!");.

The difference, here, is that we don’t need to name the class that CountToTen belongs to, because we’re already in that class, and the compiler can find it without the class name.

When our main method runs, it will encounter the first occurrence of CountToTen and jump over and run the for loop contained there. After that completes, execution will return to the main method again, where it will encounter the second occurrence of CountToTen, and jump back and do it all over again. When the second occurrence completes, we return back to the main method and then reach the end of the method.

When we jump into a method like this, it is called a method call or a method invocation . Or you might say we’ve called the CountToTen method or invoked the CountToTen method.

We’ll explore some of the details of methods in the rest of this tutorial, but hopefully even just this simple example is enough to illustrate their power and value. We get two main things out of a method:

  1. Code can be reused. Instead of copy and pasting, which becomes troublesome if you realize you need to change something and have 88 copies of it, you can define it in one place.
  2. A small, single, focused chunk of code can be given a name that describes what it does. Even if you don’t use a method more than once, it still can be valuable to divide it into different, focused methods.

Methods and Top-Level Statements

While you might be writing code with the “traditional” model shown above (with the namespace, class Program, and public static void Main), you may also be using the more concise top-level statement model. If so, your starting code would have looked more like this:

using System;

for (int number = 1; number <= 10; number++)
    Console.WriteLine(number);

for (int number = 1; number <= 10; number++)
    Console.WriteLine(number);

This flavor still packs your code into a Main method (more or less), but you can still make methods if you want. The rules are slightly different.

You can’t use public, so just leave that off. And you can actually leave off the static as well, though perhaps we’ll keep that on, for now.

You will place methods at the bottom of all of your other statements, which means our final version might look like this:

using System;

CountToTen();
CountToTen();

static void CountToTen()
{
    for (int number = 1; number <= 10; number++)
        Console.WriteLine(number);
}

info

Due to how top-level statements work, you can actually intermix statements and methods. I recommend you don’t intermix them. Furthermore, while that means you could put all methods at the top, for reasons we’ll see later, I recommend that you put your methods after all of your statements, as shown in the code above.

Passing Data to Methods

Methods can be more than just a fixed collection of statements that run on-demand. We can give them data to use while performing their job.

We’ve seen this before, when we call Console.WriteLine("Hello World!");. Here, we give the WriteLine method some data to take into account when doing its job. It happens to use that to display the text in the console window.

Let’s suppose we wanted to be able to count to five or fifty instead of just ten. While we could go make CountToFive and CountToFifty methods, the better option is to allow us to supply our method with the number to count to:

static void CountTo(int max)
{
    for (int number = 1; number <= max; number++)
        Console.WriteLine(number);
}

We can now call this like so:

CountTo(5);
CountTo(50);

The int max part, between the parentheses, is a special type of variable called a parameter . It differs slightly from variables we’ve seen in the past, which are officially called local variables . The difference is that parameters are initialized by the calling code–Main in our case–instead of within the method itself.

Multiple Parameters

You can add multiple parameters to a method by listing them between the parentheses, separated by commas:

static void Count(int start, int end)
{
    for (int number = start; number <= end; number++)
        Console.WriteLine(number);
}

Then calling it like this:

Count(10, 20);

tip

There aren’t any limits on the number of parameters to speak of, but usually, a method with more than a handful of parameters is trying to do too much all at once. Keep it to a relatively small number.

Returning Data from Methods

We sometimes want to get data out of a method. There are a handful of choices for doing this, but the primary mechanism is with a return value .

Let’s suppose we find ourselves needing to get a number from the user in a particular range. We could write the following method:

static int GetNumber()
{
    int value;

    do
    {
        Console.Write("Enter a number between 1 and 10: ");
        string input = Console.ReadLine();
        value = Convert.ToInt32(input);
    } while (number < 1 || number > 10);

    return value;
}

Most of this code is stuff we have been doing for several tutorials. The two important differences is that instead of void, we have put int before the method name.

This is the method’s return type. A return type of void means, “I don’t return anything.” A return type of int means, “I will give you back an int when I’m done.”

The last line in that method, return value; is the thing that picks what that return value actually is.

A return statement like that typically appears on the last line of a (non-void) method, but can also appear anywhere else in the method as well.

In fact, we could have written the above code this way:

static int GetNumber()
{
    while (true)
    {
        Console.Write("Enter a number between 1 and 10: ");
        string input = Console.ReadLine();
        value = Convert.ToInt32(input);

        if(value >= 1 && value <= 10)
            return value;
    }
}

That code may or may not be more clear, but it does show how you can use a return statement anywhere.

tip

Even a void method can use a bare return; statement anywhere to bail early from a method.

Methods, of course, can both return a value and have parameters.

A method cannot return multiple values, though we’ll soon see ways to make new types that combine multiple values into a single thing. If you need to return multiple values, that is how to approach it.

Method Overloads

You can define multiple methods with the same name, but they must differ by the number or types of their parameters. When two methods differ by only their parameter list, they are said to be overloads of the method.

warning

When you use the top-level statement style, you actually cannot overload methods. But this is a special circumstance, and you can generally create method overloads in other situations.

Given that everything has a specific type, in C#, you might be wondering why you can do the following:

Console.WriteLine("Hello");
Console.WriteLine(42);
Console.WriteLine(true);

There’s an easy answer! There are something like 18 overloads of the WriteLine method, each with a different parameter type, plus one with no parameters at all (Console.WriteLine()).

The same is true for the Convert class’s methods. Convert.ToInt32 has an overload with a string parameter–the one we’ve been using the most–as well as one with a float parameter, a double parameter, and so on.

When you overload a method, make sure that the two methods are doing the same basic job, just with different inputs.

Expression Bodies

All of the methods we have seen so far have used a block body–a set of statements contained in curly braces. If the entire body of a method could be represented as an expression, we can define a method with an expression body instead. For example, consider this method:

static void AddOne(int number)
{
    return number + 1;
}

We could swap that for this method, and it would be identical:

static void AddOne(int number) => number + 1;

This makes for nice, compact, but easy to read methods (at least, once you get used to a second style). I generally recommend using expression bodies whenever it can be done that way. (It is rare for an expression body to be less clear than a block body.)