static void DoSomething() { /* statements here */ }
.static void DoSomething(int someParameter) { /* ... */ }
) and then passing the argument to use for that parameter in parentheses (DoSomething(4)
).static void DoSomething(int a, int b)
and DoSomething(4, 14)
.static string GetText() { return "hi"; }
, and then when called, string result = GetText();
.static int DoubleNumber(int number) => number * 2;
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:
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.
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.
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.
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.
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.
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.)