C# from the ground up

Methods: a name for a piece of work

Writing your own methods, passing values in, giving a value back, default values for parameters, and the short arrow form.

Part 6 of 13

Intermediate16 min readBack to Courses

A name for a piece of work

A method takes the steps you were about to repeat, gives them one name, and lets the name carry the meaning. Money(amount) says what the text is for, where amount.ToString("F2") only says how it is made. The work happens once, the explanation is at the call site, and a fix happens in one place.

A method is written inside a class and marked static when it does not belong to one particular object. Every method needs a return type before its name: the type of the value it hands back, or void when it hands back nothing at all.

Parameters, and their defaults

The parameters in the parentheses say what the method needs to be given, one type and one name each. At the call, the values go in by position, so their order is part of the method. A parameter can carry a default: decimal rate = 0.2m means a caller may leave it out and gets twenty percent, while a caller who cares may pass its own.

Defaults have to come after the parameters that are required, because the call fills them left to right. A default is a fixed value, not a calculation — if the value needs thought, work it out inside the method instead.

Giving something back

return ends the method and hands one value to the caller, and the value has to match the declared return type. A void method may use return on its own to leave early, but it has no value to hand over. A method that prints is a method you cannot use in a calculation, so the useful ones return values and let the caller decide what to do with them.

One last shape: when a method is a single expression, it can be written with an arrow instead of braces — static decimal Half(decimal amount) => amount / 2;. Same method, no ceremony.

Four method shapes

using System;

class Program
{
    // A method that hands a value back says which type.
    static decimal WithTax(decimal price, decimal rate = 0.2m)
    {
        return price * (1 + rate);
    }

    // void: it does something and gives nothing back.
    static void Show(string name, decimal sum)
    {
        Console.WriteLine(name + ": " + sum.ToString("F2"));
    }

    // A one-line method can use an arrow.
    static decimal Half(decimal amount) => amount / 2;

    static void Main()
    {
        Show("Price", 10m);                     // 10.00
        Show("With tax", WithTax(10m));         // 12.00
        Show("No tax", WithTax(10m, 0m));       // 10.00
        Show("Half", Half(9m));                 // 4.50

        // A value that comes back can feed another call.
        Show("Half with tax", WithTax(Half(9m)));   // 5.40
    }
}

One price, four ways

Run it and read the four lines. Then add a method that applies a discount percentage, and use it on the price before the tax is worked out.

Output
Click "Run" to execute your code

Give a method the name of its answer, not of its steps. WithTax and Money both read well at the call site, which is the only place most readers see them.

The call stack, one frame at a time

When a method is called, the runtime makes a frame for it: room for its parameters and its local variables. The frame lives until the method returns, and then it is gone. The next call gets a fresh frame, so a local declared inside a method starts from its initial value every time.

That is why a method cannot remember a value between calls unless the value lives somewhere else, in a field of an object or in a variable the caller owns. It is also why recursion works: each call has its own copy of the parameters rather than sharing one.

Arguments go in by value

A parameter receives a copy of the argument. For an int, that means the method can change its parameter freely and the variable in the caller is untouched. For an object, the copy is a copy of the reference, so the method can change the object both names point at, but it cannot make the caller point somewhere else.

That distinction explains a class of bugs that look impossible: a method that reassigns a parameter to a new object has done nothing the caller can see, while a method that calls a setter on it has changed the shared object.

Two keywords let a method change the caller variable after all. ref means the caller supplies a variable that already has a value and the method may read and replace it; out means the method must assign it before returning, and the caller may pass an uninitialised one. Both are written at the call as well as in the signature, so the reader can see that this call is allowed to reach back. Use them when a method genuinely has two results, and prefer a return value when it has one.

Three method mistakes: a path that reaches the end without returning a value, which the compiler reports as not all code paths return a value; putting a defaulted parameter before a required one; and expecting an assignment to a parameter to change the variable in the caller, since the parameter is a copy.

Try these next

  • Write a method that takes two decimals and returns the larger, then call it with the arguments in both orders.
  • Add a default rate to a tax method, call it with and without the second argument, and print both answers.
  • Write a void method that prints a label, then change it to return the text and print at the call site instead.

A greeting built by three methods

Run it and read the four lines. Then change the default opening, and add a line that counts the words in the formal greeting.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does the word before a method name say?