Methods in C#

Methods

The Crash Course

  • Methods are a way of taking a chunk of code that does a specific task and putting it together in a way that is reusable.
  • Stuff can be returned from a method by specifying the return type in the method definition and then using the return keyword inside of the method anywhere you want to return a value, very similar to C, C++, and Java.
  • Parameters can be passed into a method, also in a way that is very similar to C, C++, and Java.
  • Multiple methods with the same name can be made (called method overloading) as long as their method signatures are different.
  • Recursion is where you call a method from inside itself and requires a base case to prevent the flow of execution from digging deeper and deeper into more method calls, eventually killing itself in a StackOverflowException.

Introduction

Think about a program like Microsoft Word or a game like Gears of War. Think about how much code goes into creating something that big. They're enormous! Especially when compared to the little tiny programs that we've been working on so far.

How do you think you could manage all of that code? Where would you look if there was a bug?

As programmers, we've learned that when you have a large program, the best way to manage it is by breaking it down into smaller, more manageable pieces. This even gives us the ability to reuse these pieces. This basic concept is one we'll see over and over again, and it is called divide and conquer.

C# provides us with a feature that allows us to break down our program into more manageable pieces. This feature is called a method. Methods are sometimes called functions or procedures in other programming languages. You can take a piece of your code, place it inside of a method, and then access it whenever you want. We can more easily locate a bug or problem in a program because we can pinpoint it to a very specific method. In addition, we can reuse a method as often as we want, so we don't have to reinvent the wheel and save ourselves tons of time.

In this tutorial, we'll look at how to make a method, use a method (called calling a method), get data back from a method, and send data to a method. Next, we'll look at creating multiple methods with the same name, a process called overloading. We'll then take another look at the Math and Console classes, which we've been using in previous tutorials, this time, with our new knowledge about methods. We'll wrap up our discussion about methods with a brief discussion on a powerful but often confusing (for new people) trick that you can use with methods called recursion.

Creating a Method

Let's get started by creating our first method. If you start with a brand new project, you'll see yourself staring down at code that looks like this:

Console.WriteLine("Hello, World!");

Strictly speaking, this code defines a method known as the main method (also sometimes called the entry point). The main method is a special method where your program begins execution but can jump over to other methods when asked and then return back when the method finishes.

Console's WriteLine method is a great illustration of a method, and even this single-line program does exactly that: the main method begins running, the flow of execution hops over to WriteLine, performs that task, then comes back to our main method to run the next statement.

But our main method is very much a method in the same vein as WriteLine, that just hasn't been obvious or clear before now.

We're at a point where we want to be able to make many methods, not just our main method, and that is what we'll cover in this tutorial. In short order, we'll start organizing methods into different classes, much like how WriteLine, Write, and ReadLine all live in the Console class. But for now, we'll simply make methods that live directly inside the main method. (I should point out that we'll do this a bunch for the main method, but in other circumstances, we don't often put methods inside of other methods. They're usually independent of each other.)

Let's start by adding a new, second method into our main method:

Console.WriteLine("Hello, World!");
 
static void CountToTen()
{
    for (int index = 1; index <= 10; index++)
    {
        Console.WriteLine(index);
    }
}

The static void CountToTen() line is what begins a new method definition. We'll talk more about what static means later, but for now, we're just going to apply that to the methods we create. It is a tool that helps ensure we build methods that are independent of each other as much as possible.

We'll also talk about the void thing in depth soon, but the short version is that this says the method doesn't produce any results. It just does something.

The CountToTen part is the name of the method we're declaring, and the parentheses indicate that we're making a method.

After this comes curly braces and any statements we want to run when the method is asked to run. The curly braces and their contents are called the method body. They dictate how the new method should work. But all of the statements we've been talking about up until now can work in any method. So we can put if statements and loops, declare variables, and even call other methods in our methods.

The for loop inside of CountToTen should look pretty familiar since we've done that kind of stuff in earlier tutorials.

But it is time to play a game. What would you guess will happen when we run this program? What will it display?

If you guessed that it will display "Hello, World!" and not the numbers 1 through 10, you are right.

This code declares a new method but never uses it. The act of creating the method does not immediately call it, even though it makes an appearance in the code itself.

We get "Hello, World!" displayed because our main method specifically told it to run. We can ask our CountToTen method to run by calling it as well:

Console.WriteLine("Hello, World!");
CountToTen();
 
static void CountToTen()
{
    for (int index = 1; index <= 10; index++)
    {
        Console.WriteLine(index);
    }
}

In fact, we could call it multiple times:

CountToTen();
Console.WriteLine("Hello, World!");
CountToTen();

Now it will call it twice and put a "Hello, World!" between them.

One thing to note is that calling CountToTen has not demanded putting a class name ahead of it like we have to do with Console.WriteLine. That is because WriteLine belongs to Console, but CountToTen belongs to our main method, which makes it so we can access it without extra qualifiers before it.

Eventually, we'll make methods that live in classes. But any method can contain child methods if they want. (This happens often for the main method, but only rarely for other methods.) That means you could make another method inside of CountToTen if you wanted, and then one inside of that.

Methods can call other methods, so it allows us to jump around as needed, digging down into deeper method calls as needed, before completing them and returning up to earlier method calls. When the main method ends, the program ends.

Placement of Methods

We placed our CountToTen method at the bottom of our main method. However, strictly speaking, a method defined in another method can live anywhere within that method—at the bottom as we've done, at the top, or even intermixed among the other statements. That can get very confusing very fast, so I'm going to recommend always putting them at the bottom of the main method for now until you get more comfortable with C# programming.

Given that we've already talked about putting types in your Program.cs file, I also want to point out that your main method ends when a new type definition is encountered. So the general order would be to put the statements you want the main method to do first, then all of your methods that live in your main method, then any type definitions you want to use:

DisplayAMonth();
DisplayAnotherMonth();
 
static void DisplayAMonth()
{
    Console.WriteLine(Month.January);
}
 
static void DisplayAnotherMonth()
{
    Console.WriteLine(Month.July);
}
 
enum Month { January, February, March, April, May, June, July, August, September, October, November, December }

Adding Parameters

Right now, our methods are totally independent of each other. What if we want them to be able to share information?

That is typically done by passing parameters into the method and returning values as a result or response. We'll talk about both of these next.

To send data to a method, you do so by making a list of parameters between the parentheses when the method is defined. We could use this to make our CountToTen method be flexible enough to count to any number:

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

That int number part looks like a variable declaration, and it is! It is a type of variable called a parameter. This is just like any other variable, with the exception that the method that declares it does not need to initialize it. It will be given an initial value when it is called by the method that calls it.

We can now call that by passing in an argumenta value that is assigned to the parameterwhen we call it:

CountTo(10);
Console.WriteLine("Hello, World!");
CountTo(50);

This code will count to 10, display "Hello, World!", then count to 50. This method is much more flexible because it can count to anything, not just to 10. Though the downside is that it is (a tiny bit) more complicated. You don't want to make things more flexible than necessary, but often, a little bit of flexibility by adding parameters makes the code a whole lot more reusable.

This, by the way, is exactly how Console's WriteLine method works. It has a parameter for what text to display, and we just call it with different text in different circumstances.

Returning Stuff from a Method

Methods are created to do something. Often, though, we want a method to actually do something and give us the results back. A method has the option of doing this—giving something back. This is called returning something.

So far, all of the methods we've looked at haven't returned anything. If you'll look back, you'll see that we've been putting the void keyword before our method name when we create a method. void indicates that the method does not return anything. If we want to return something, we start by swapping out void for the type we want to return.

Inside of the method, we can indicate what value we want to return by using the return keyword:

static int GetNumber()
{
    return 4;
}

Here, the return type is int instead of void, and we end the method with a return statement, indicating that this method should produce a 4 every time it is called. This is the absolute simplest method that returns something, and we'll deal with more complex examples in a minute.

We're allowed to call this method without doing anything with the return type. Our main method could look like this:

GetNumber();

But typically, when we call a method that returns something, we actually want to do something with it. That return value can be used in an expression or stored in a variable:

int number = GetNumber();
Console.WriteLine("The number you got was " + number + ".");

But we can make the code way more interesting (and complicated) than a plain old return 4;. The code below returns a number chosen by the user:

static int GetNumber()
{
    Console.Write("Enter a number: ");
    string input = Console.ReadLine();
    int parsedNumber = Convert.ToInt32(input);
    return parsedNumber;
}

This represents a very meaningful chunk of code that can easily be reused multiple times to get numbers. Though we could make it a bit more flexible by allowing the calling method to specify the text to use to prompt the user:

static int AskForNumber(string prompt)
{
    Console.Write(prompt);
    string input = Console.ReadLine();
    int parsedNumber = Convert.ToInt32(input);
    return parsedNumber;
}

We can use this repeatedly:

int x = AskForNumber("Enter the row number: ");
int y = AskForNumber("Enter the column number: ");
Console.WriteLine("You've chosen the coordinate (" + x + ", " + y + ")");

Neat, right?!

Returning Early

While return statements often land on the final line of a method, they don't need to always be the last statement. This is especially true if you have if statements and loops.

Returning Early from a void Method

A void method will naturally end when the flow of execution hits the end of the method. But you have an option to end a void method before the bottom of the method by using a plain return; statement:

static void MaybeReturnEarly(bool returnEarly)
{
    if (returnEarly) { return; }
 
    Console.WriteLine("Did not return early.");
}

Multiple Parameters

Some methods need more than one piece of information to do their job correctly. This can be achieved by listing multiple parameters between the parentheses in a method definition, separated by commas. For example:

static int GetNumberBetween(string prompt, int min, int max)
{
    int parsedNumber;
    do
    {
        Console.Write(prompt);
        string input = Console.ReadLine();
        parsedNumber = Convert.ToInt32(input);       
    }
    while (parsedNumber < min || parsedNumber > max);
 
    return parsedNumber;
}

This code defines a method that will ask the user to type in a number until they enter one within the target range.

It could be improved by telling the user they entered bad data, but I'm going to leave that out for now, since it doesn't help illustrate the concepts related to methods any better.

Revisiting the Console and Math Classes

In light of everything we've learned about methods here, let's go back and revisit our old friends in the different classes we've been using.

Somewhere out there is a definition for WriteLine in the Console class. It has a string parameter, giving it flexibility about what is displayed. Also in the same class is a ReadLine method that has a string return type and no parameters.

In fact, all of the methods we've seen have been defined in a way that is quite close to what we've been doing. (Though I imagine actually getting characters displayed on the screen is a bit more involved than the simple code we've done so far.)

Method Overloads

In relation to the methods we've seen in other classes, there's one other topic worth bringing up.

You can name two methods the same thing, as long as they have different parameter lists. Multiple methods with the same name are referred to as overloads, or you might say the method is overloaded. The compiler figures out the right one to call based on the code that calls/uses the method.

As an example, the Console.WriteLine method is actually many overloads, not just a single method. They differ by parameter type. There is a WriteLine with a single string parameter, one with a single int parameter, one with a single bool parameter, etc. The compiler sees a line of code like Console.WriteLine(3); and knows to call the version that has an int parameter, simply because 3 is and must always be an int.

There are two limitations to method overloads.

First, return types do not play a role. Only parameter types and counts. You cannot make two methods that differ only in return type.

Second, methods that live inside other methods cannot currently be overloaded. That is how we've been making methods so far in this tutorial, which means overloads are currently off-limits to us. But we'll soon be making methods that aren't attached to other methods, and it will become an option then. In other words, we can use method overloads right now, but we cannot make them just yet. But we'll get there.

The Minimum You Need to Know About Recursion

There's one additional trick people use with methods that I think is worth bringing up here. It's a complicated trick, so if you want to skip it right now, feel free to. But it comes up often enough that I wanted to describe the basics so you can start thinking about how it might be useful.

A method can call any method, and that includes itself. When a method calls itself, it is called recursion, or you might say the method is a recursive method. Here is a trivial but broken example:

static void BrokenRecursiveMethod()
{
    BrokenRecursiveMethod(); // BROKEN!
}

The problem here is related to the fact that we get into the method, then immediately call into it again. It is safe to call a method like this in succession, but the problem is that, in this particular code, we never come back up. We keep going deeper and deeper. It takes memory to call methods, and eventually, we run out of space to call deeper into additional method calls, and the program crashes with a thing called a stack overflow.

We could fix this by ensuring that we don't dig deeper forever but eventually start coming back up.

There are a lot of problems that have complicated solutions without recursion, but with recursion, have very elegant solutions. These are problems that are defined in relation to itself somehow.

The traditional example of this is the math concept of factorial. The factorial of 1 is defined to be 1. The factorial of any number bigger than 1 is to multiply it by all numbers less than itself, down to 1. Or stated differently, the factorial of 1 is 1. The factorial of any other numberlet's call it nis n * the factorial of (n-1). So the factorial of 2 is 2 * 1. The factorial of 6 is 6 * 5 * 4 * 3 * 2 * 1. That is relatively easy to turn into recursive code:

static int Factorial(int number)
{
    if (number == 1) { return 1; }
 
    return number * Factorial(number - 1);
}

The code looks almost like the definition of factorial, which is nice!

If that doesn't make a whole lot of sense right now, worry not. It's a tricky topic that takes a lot of getting used to. Just be aware that it is a thing you can do and is a powerful tool, but it is also a bit tricky to think recursively.

What's Next?

That wraps up our introduction to methods. Methods are extremely powerful, and we are going to be using them all through the rest of these tutorials (in fact, we've been using them, without knowing what they were, up to now).

We've seen how you can return values from a method and pass information into methods as parameters.

We've also covered a couple of slightly more advanced topics with methods, including overloading and recursion. If you didn't fully grasp those concepts, don't worry too much—they are a bit more advanced, after all. But it is important that you be aware that they exist, and as we run into them again, you'll continue to become more familiar with them.

Next, we move into the world of object-oriented programming—a particularly poor buzzword that alludes to some very cool stuff in C#! Next stop: classes!