Understanding functions/methods is critical to learning C#. This tutorial will introduce you to the concept and explain how they might be used.

Why is this important to learn about?

When learning how to program, people will consistently tell you to write "reusable code". They'll tell you things like "Don't Repeat Yourself" (DRY). Methods are one of the primary ways of writing re-usable code. They're also a way to logically arrange and organize your code. Even if you're not going to re-use a certain block of code, it may be worth extracting it into a method because it's more readable and keeps a particular chunk of code from getting too complicated. But before we get too deeply into that, let's visit some definitions:

Definitions of terms used.

Function (also called "subroutines", mainly in VB.NET) "a sequence of program instructions that perform a specific task, packaged as a unit. This unit can then be used in programs wherever that particular task should be performed". Basically, a function is a chunk of code, that is callable by other code. It can perform some calculations and return a value, or it can perform some actions/tasks without returning a value back to the caller. Functions can (but don't have to) take data as "arguments" to operate on or use in it's tasks.

Method - A method is simply a function attached to a class. In some languages, the distinction is important, because functions can be defined arbitrarily. In C# however, (named*) functions must belong to a class, so the distinction is unimportant. You can use the words "function" and "method" interchangeably for the most part. This tutorial will focus specifically on Methods.

*There is such a thing as an "anonymous" function in C# that isn't attached to a class, but that's far beyond the scope of this tutorial. Don't worry about them now.

Type (also called "data type"): "a classification identifying one of various types of data, such as real, integer or Boolean, that determines the possible values for that type; the operations that can be done on values of that type; the meaning of the data; and the way values of that type can be stored."

Argument (also called "parameter"): "a special kind of variable, used in a subroutine [function] to refer to one of the pieces of data provided as input to the subroutine"

Note:All examples were created using Visual Studio 2013, targetting the .NET Framework 4.0. We'll do our best to point out anything that might not work in older versions.

Methods

First of all, let's look at a very basic function:

void HelloWorld()
{
Console.WriteLine("Hello World");
}

Very simple, yet not necessarily self-explanatory. A line by line breakdown:

But that's still just part of the story. As I mentioned in the definitions, functions don't exist in a vacuum. They have to be attached to a class. Here's a more complete example, with a very simple class:

Notice there's a bit more going on there. Now we see the keyword public.

Here's the full code that we will be using and modifying for all the following examples (if you'd like to follow, start a new console project):

using System;
namespace HW.WConsole
{
//our class that we will be adding methods to
public class HW
{
//our example method
public void HelloWorld()
{
Console.WriteLine("Hello World!");
}
}
//the "Entry Point" for the application.
class Program
{
//When you run your program, it "starts" here.
static void Main(string[] args)
{
//create a new instance of the HW class, then call our "HelloWorld" method on it
HW myInstance = new HW();
myInstance.HelloWorld();
//pause the console so we can see what happens when we run the code
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
}
}

When the program starts, it starts in the "Main" function. It creates a new instance of our class, then calls the function on it named HelloWorld. What happens when we "call" a function?

Well, in simple terms, "control" of the program leaves the "caller", and is given to the function that is called. The "Main" method in the Program class calls the HelloWorld method in the HW class. It transfers control to that method. When that method is done running, it transfers control back to the Main method, right where it left off. If you'd like to see this in action, press "F11" in Visual Studio (or choose "Step Into" from the debug menu). Once the program starts, the debugger will stop on each line. Each time you press F11 it will step to the next piece of code it will run. When you step into the second line of the Main method, you will notice that the highlighted line jumps all the way up to the HelloWorld function in the HW class. When you step out of that, you'll notice that it goes back to where it left, before continuing in the Main method.

That was an example where the method doesn't send any data back to the caller. But we could do that if we wanted.

Return Types

Return types tell the compiler what type of data the function is going to return, if any. We've seen the void keywords in our previous examples. What does it mean? That's the keyword that says "I don't actually return anything". It means this method is one that does things, but you don't get a response back. But you can replace the void keyword with the name of any legal .NET Type. Let's see an example that returns a string to the caller:

We've replaced the void keyword with string. We've also added a new keyword: return. return tells the compiler to send a particular value back to the caller, and immediately return control to the caller. We also see slightly different calling syntax: we're setting some variable (msg in this case) to the result of the function. It's just like assigning a literal value: variable on the left, assignment operator in the middle, and whatever you want to assign to it on the right. In this case, when the function runs, whatever is returned is set to the variable.

It's important to note that your method must be structured in such a way that it's impossible for it to exit without returning a value. Here's an example of one that is illegal:

At first glance, it's fine. Of course, if DateTime.Now isn't greater than Constants.StoredDate, the return statement wouldn't be run, and there's no other return statement in that execution path. Therefore, it's invalid. Even if you personally know that it's impossible for the code to ever not hit your return statement, as long as the compiler sees the possibility, it will not allow you to compile. To correct our previous example:

Remember, as mentioned earlier, return not only sends a value back to the caller, but also control. So if it does hit the return "Now"; line, nothing after that in the method is run. If it doesn't, by default it will hit the return null;. null is a valid value for any class (note: C# "value types" are not classes and thus cannot be null. An example of this is an int, or a DateTime.)

Since return gives back control, any code below an inevitable return can never run; the compiler will warn you about this:

This won't stop the program from compiling, but the compiler will let you know there's code there that can never be reached, no matter what.

One last note about returning: you may see the return keyword in void functions, but "naked" (with no value/expression to the right of it). The return keyword in a void method acts similarly to the break keyword in a loop; it causes it to exit immediately. In the case of a method, it returns control to the caller. Example:

This example exits the method if the provided string is null, otherwise it prints it to the console.

Arguments

So, we've seen functions that do things without requiring input, as well as some that return data also not requiring input. So what do we do when we need to write re-usable code that requires input? We give our function arguments:

This example actually shows two functions, one that takes a single argument, one that takes two. (Also, it shows that you can return the result of an expression, not just a variable).

Arguments are comma-separated. You specify the type in the method definition, but not when calling. You can use any valid type as arguments.

Arguments are treated as variables that were created in the function. Therefore, arguments are said to be scoped to the function.

Scope

This is a good spot to discuss "scope". Scope is what we might describe as "where does an object live and what does it have access to"? When we create a variable in a function, it only lives for as long as that function is being called. When the function returns (or ends without returning for a void function), the variables are destroyed. When the function is called again, the variables are re-created. Variables in one function do not have access to variables defined in another function, which makes perfect sense since the other variables don't even exist until the other function is executing.

Back to arguments.

Your function can define as many arguments as you want. However, you must provide a value for each of them when calling*.

Here's an example of an illegal call, using the code defined in the previous example:

//illegal, will not compile
int result = myClass.Add(6);

* There is a way to provide a default value for an argument. This will be discussed near the end of this tutorial.

Calling functions

We haven't yet discussed how calling functions actually works.

So far, we've just seen examples of calls from the Main entry point of the program. We call the method through the object it's attached to, with the dot (.) operator. The function call must include an open and close parenthesis. If you leave these off, you're actually referring to the function as an object, not a function. This usage is somewhat more advanced and will be discussed in a later tutorial in the series (more info).

Remember, methods are members of classes. You must call the method on a class. But what about calling a method from a class from another method in the class?

Note how we don't have to provide the object's name when we're calling Step1 and Step2 from the DoSomething method? That's because they're all members of the same class. Implicitly, it treats calls to functions defined in the same class as if it were prepended with the this operator. (this always refers to the instance of the class that the function is being run from).

Now, let's say that we didn't want to expose Step1 and Step2 to "the world" (which is everything). We want these to be available to us inside of the HW class, but we don't want other things to be able to call into them. That brings us to:

However, I'd like to cover two of the more important ones: public and private.

Public methods are part of the "external interface" for a class. They are the functions exposed for the outside world (i.e. any other class/function/whatever) to call.

Private methods are only available inside the class. They're there to help you logically arrange your code, to be called into from other methods inside the class. These are not visible from outside the class, and if you try to call a private method on a class outside the class, the code will not compile.

Since Step1 and Step2 are both being called from DoSomething (which is inside the HW class), they are allowed to be called. However, if I tried this:

int result2 = myClass.Step1(1, 2);

from the Main method, the code will not compile.

Best practices are to expose as little as necessary for an object to do its job. This prevents you (or another developer working on the same project) from accidentally calling a method out of order, or without setting the class's state up correctly.

If you do not provide an access modifier, methods default to private. However, I recommend always providing the modifier.

Recursion

Recursion is what it's called when a function calls itself. In other words, the function is defined in terms of itself. A classic example is the mathematical "Factorial" operator. If you've been in an advanced math class, you may have seen it (the ! operator) defined something like this:

f(x) = | x > 0 -> x * f(x-1)
| x <= 0 -> 1

In English, that means "factorial of x is defined as: when x is greater than zero, f(x) is x multipled by factorial of (x-1). When x is zero (or less), f(x) is 1.

Note that the function Factorial actually calls the function Factorial.

You can solve any iterative problem with recursion, and sometimes more efficiently. However, there's always somethign to remember: each time the function recurses, it pushes another function pointer onto the stack. Recursive functions are effectively started from first-to-last, then evaluated last-to-first. This is the "first-in, last-out" principle. Since each call requires the result of the next call to be evalueated, you must always have an exit case. What that means is there must always be at least one non-recursive path through your function, or you will create an "infinite loop" (though it will not run infinitely. The stack has a finite size, once you've pushed too many function calls onto it, your program will crash with a "StackOverflowException"). Our "exit case" is the check to see if number is greater or equal to 0. In that case, we don't recurse, we simply return 1.

I've listed each factorial by it's definition below it. Each "layer" represents a recursive call. Since we don't know what 3! is until we know what 2! is, and we don't know what 2! is until we know what 1! is (etc...), we have to solve each first, until we get to the non-recursive case (in this case, 0! is 1. There's no more recursion, 0! is a constant value). Then we start replacing our value as we traverse back up the stack. 1! is replaced with 1, 2! is replaced with 2, and 3! is replaced by 6.

As I mentioned, you can solve any iterative problem with recursion, but you don't necessarily want to. In Functional Programming, recursion is often favored over looping (iteration), but in a more classical language like C#, recursion isn't as common of a pattern. It's a valuable and viable tool to learn, but remember, just because you have a hammer doesn't mean every problem is a nail.

Overloading

Methods are defined in terms of their signature. A method signature is defined by Microsoft as:

Quote

The signature of a method consists of the name of the method and the type and kind (value, reference, or output) of each of its formal parameters, considered in the order left to right. The signature of a method specifically does not include the return type, nor does it include the params modifier that may be specified for the right-most parameter

What does this mean for us? Well, sometimes there's more than one way to do a certain thing. Let's use a contrived example:

Here we see that we can make a request using a Uri object, or a string. Interestingly enough, WebClient.DownloadString is already overloaded to take either a Uri or a String.

Overloads can call each other, as shown. The compiler knows how to check the call you're making and match it to the correct signature. They don't have to be defined in terms of each other, there doesn't have to be a "base" overload. They can all do completely different things. One thing to note is that a method's signature specifically doesn't include its return type. So that can't be the only difference between two methods.

params Arguments

You may be familiar with the String.Format method. If so, you'll have noted that it can take an arbitrary number of arguments (excluding zero). You may have wondered how this is possible. It is, and it's called a "params array".

The params array must be the last parameter in the list. If you want to take objects of more than one type, you can use an params object[], though you must be careful to cast the resulting objects correctly.

Static Methods

We've been looking at examples that include this:

static void Main(string[] args)

But we haven't discussed what static means. static defines a method to be "shared", or not associated with a particular instance of a class. A non-static method must be called from an instance of an object. But a static method must be called from the class name itself, when it's being called externally from a class.

Note that we didn't create a new instance of HW, we just called PrintToConsole directly on the HW class name.

If, however, we tried this:

HW myClass = new HW();
myClass.PrintToConsole("test");

We get the following error:

Quote

error CS0176: Member 'HW.WConsole.HW.PrintToConsole(string)' cannot be accessed with an instance reference; qualify it with a type name instead

Why do we want to do this? Well, if there's some set of functionality that you want logically available from a particular class, but it doesn't have to operate on the class's data, it's a great choice. Take the static methods on the String class. String.Format and String.IsNullOrWhiteSpace. Both are static, because they don't have to be called from an instance of a string.

Static methods can be called from inside a class without using the class's name:

Note that in our SetValues method, we have the equals sign (assignment operator) in our argument list. This tells the argument what value it should have if a value is not provided. In the provided example, Name would be set to "Curtis", Age would be set to "30", and HairColor would still be null (the default value for strings and all reference types).

Now, this would make it awkward if all we wanted to provide was a HairColor. However, with "Named" arguments, you can specify which argument you want to give:

myClass.SetValues(hairColor: "brown");

Note that all optional parameters must come after all non-optional parameters.

In Conclusion

Hopefully this has provided you a solid basis for understanding the how and why of methods. If you have any questions, please leave them in the comments section.