Skip to main content

Methods and Parameters: Reusable School Rules – C# | Lesson 34

Published / updated

Introduction​

In Lesson 32, you wrote the grade switch inside the program. If a report card, a result screen, and an export all need grades, you would copy it three times. When the school changes the scale, you must find and fix every copy.

A method gives a piece of work a name, so you write it once and call it from anywhere. In this lesson, the school rules from Lessons 32 and 33 move into methods in a new file, SchoolRules.cs.

By the end of this lesson, you will be able to:

  • Write methods with parameters and return values.
  • Use optional parameters, named arguments, and overloads.
  • Explain the difference between normal, ref, and out parameters.
  • Use the Try pattern and params.
  • Write one-line methods and local functions.

Why this matters​

Real applications are built from thousands of small methods. A good method does one job, has a clear name, and can be tested on its own. GetGrade(92) should return "A1", today and every day.

Methods are also how teams share work. In the Web API, a controller calls a service method, which calls a repository method. Every lesson from here on builds on what you learn now.

The concept​

Example: the parts of a method​

Example
public static decimal CalculatePercentage(decimal marksObtained, decimal maxMarks = 100m)
{
return Math.Round(marksObtained / maxMarks * 100, 2);
}
PartMeaning
public staticWho can call it, and how; Lessons 37 and 42 explain both words
decimalThe return type: what the method gives back; void means nothing
CalculatePercentageThe name: a verb, in PascalCase
(decimal marksObtained, ...)The parameters: the inputs the method needs
return ...;Ends the method and hands the value back

When you call it, CalculatePercentage(67m), the value 67m is an argument. Parameters are the slots; arguments are what you put in them.

Think of the school office counter. You hand in a filled form (the arguments), the clerk follows a fixed procedure (the method body), and you get a certificate back (the return value). You do not need to know how the office works inside.

Example: optional and named arguments​

decimal maxMarks = 100m makes maxMarks optional: if the caller leaves it out, it is 100. Most papers are out of 100, so most calls stay short.

A named argument writes the parameter name in the call: CalculatePercentage(marksObtained: 38m, maxMarks: 50m). It makes calls with several numbers easy to read.

Example: overloads​

Two methods can share a name if their parameters differ. This is called overloading:

CallWhich method runs
CalculateAttendancePercentage(24, 27)The one with two int parameters
CalculateAttendancePercentage("PPLAP")The one with a string parameter

The compiler picks the overload from the argument types. Here the second overload counts the days and then calls the first, so the actual calculation exists only once.

Example: three ways to pass a value​

KindKeywordWhat the method receivesUse for
NormalnoneA copy of the valueAlmost everything
By referencerefThe caller's own variable, to read and changeRare: changing a value in place
OutputoutThe caller's variable, which the method must fillReturning extra results, as in TryParse

Both the method and the caller must write ref or out, so nobody is surprised that a variable changes.

Apply it to the SMS​

The school rules now live in SchoolRules.cs. A static class is, for now, a named container for methods; Lesson 37 explains classes properly.

Show src/SchoolApp.Console/SchoolRules.cs
src/SchoolApp.Console/SchoolRules.cs
// Lesson 34: school rules as reusable methods.
// A static class is a named container for methods; Lesson 37 explains classes in full.
static class SchoolRules
{
public const int PassMark = 33;

// Grade for a percentage: the same scale as fn_GetGrade in SQL (Lesson 21).
public static string GetGrade(decimal percentage)
{
return percentage switch
{
>= 91 => "A1",
>= 81 => "A2",
>= 71 => "B1",
>= 61 => "B2",
>= 51 => "C1",
>= 41 => "C2",
>= 33 => "D",
_ => "E"
};
}

// An expression-bodied method: a one-line method written with =>.
public static bool HasPassed(decimal percentage) => percentage >= PassMark;

// An optional parameter: maxMarks is 100 unless the caller says otherwise.
public static decimal CalculatePercentage(decimal marksObtained, decimal maxMarks = 100m)
{
return Math.Round(marksObtained / maxMarks * 100, 2);
}

// Two overloads: the same name, different parameters.
public static decimal CalculateAttendancePercentage(int attendedDays, int markedDays)
{
if (markedDays == 0)
{
return 0;
}
return Math.Round(100m * attendedDays / markedDays, 2);
}

public static decimal CalculateAttendancePercentage(string week)
{
int attended = 0;
foreach (char day in week)
{
if (day != 'A')
{
attended++;
}
}
return CalculateAttendancePercentage(attended, week.Length);
}

// The Try pattern: return true or false, and hand back results through out parameters.
public static bool TryParseAdmissionNumber(string text, out int year, out int serial)
{
year = 0;
serial = 0;
string[] parts = text.Split('-');
return parts.Length == 3
&& parts[0].Equals("ADM", StringComparison.OrdinalIgnoreCase)
&& int.TryParse(parts[1], out year)
&& int.TryParse(parts[2], out serial);
}

// ref: the method changes the caller's own variable.
public static void ApplyPayment(ref decimal balance, decimal amount)
{
balance -= amount;
}

// params: the caller can pass any number of marks.
public static decimal Average(params decimal[] marks)
{
if (marks.Length == 0)
{
return 0;
}

decimal total = 0;
foreach (decimal mark in marks)
{
total += mark;
}
return Math.Round(total / marks.Length, 2);
}
}

Program.cs only calls the rules and prints the results:

Show src/SchoolApp.Console/Program.cs
src/SchoolApp.Console/Program.cs
// Lesson 34: methods and parameters. The rules live in SchoolRules.cs; Program.cs calls them.

// 1. Calling methods: arguments go in, a return value comes out.
decimal kabirPercentage = SchoolRules.CalculatePercentage(67m);
string kabirGrade = SchoolRules.GetGrade(kabirPercentage);
Console.WriteLine($"Kabir: {kabirPercentage}% -> {kabirGrade}, passed: {SchoolRules.HasPassed(kabirPercentage)}");

// 2. Optional and named arguments: a paper marked out of 50.
decimal outOf50 = SchoolRules.CalculatePercentage(marksObtained: 38m, maxMarks: 50m);
Console.WriteLine($"38 out of 50 = {outOf50}% -> {SchoolRules.GetGrade(outOf50)}");

// 3. Overloads: the same name, different parameters.
Console.WriteLine($"Attendance from counts: {SchoolRules.CalculateAttendancePercentage(24, 27)}%");
Console.WriteLine($"Attendance from a week: {SchoolRules.CalculateAttendancePercentage("PPLAP")}%");

// 4. out and the Try pattern.
if (SchoolRules.TryParseAdmissionNumber("ADM-2017-0003", out int year, out int serial))
{
Console.WriteLine($"Admission year {year}, serial {serial}");
}
if (!SchoolRules.TryParseAdmissionNumber("2017/0003", out _, out _))
{
Console.WriteLine("'2017/0003' is not an admission number");
}

// 5. ref: the method changes the caller's variable.
decimal diyaBalance = 25500m;
SchoolRules.ApplyPayment(ref diyaBalance, 10000m);
Console.WriteLine($"Diya's balance after a payment: {diyaBalance}");

// 6. params: any number of values.
Console.WriteLine($"Class 10 Mathematics average: {SchoolRules.Average(92m, 85m, 67m, 95m, 58m, 78m)}");

// 7. Normal parameters are copies: the method cannot change the caller's variable.
decimal mark = 67m;
AddGraceMarks(mark);
Console.WriteLine($"After AddGraceMarks(mark): {mark}");

// A local function: a method that belongs only to this file.
static void AddGraceMarks(decimal value)
{
value += 2;
}
Output
Kabir: 67.00% -> B2, passed: True
38 out of 50 = 76.00% -> B1
Attendance from counts: 88.89%
Attendance from a week: 80%
Admission year 2017, serial 3
'2017/0003' is not an admission number
Diya's balance after a payment: 15500
Class 10 Mathematics average: 79.17
After AddGraceMarks(mark): 67

Example: one rule, many callers​

GetGrade is the Lesson 32 switch, moved into a method. Program.cs calls it twice, for Kabir's 67% (B2) and for 38 out of 50, which is 76% (B1).

Any future screen calls the same method. When the school changes the scale, you change one method, and every caller gets the new rule.

Example: the Try pattern​

TryParseAdmissionNumber follows the same pattern as int.TryParse: it returns true or false, and hands back the results through out parameters.

For ADM-2017-0003, it returns true with year 2017 and serial 3. For 2017/0003, it returns false, and the caller uses out _ because it does not need the parts. The method must set every out parameter, even when it fails, which is why it starts with year = 0.

Example: ref changes the caller's variable​

ApplyPayment(ref diyaBalance, 10000m) takes Diya's 25,500 and reduces the caller's own variable to 15,500.

Most code avoids ref. A method that returns the new balance, balance = ApplyPayment(balance, 10000m), is easier to read and test. You still need to recognise ref in existing code.

Example: params​

Average(params decimal[] marks) accepts any number of arguments. The call Average(92m, 85m, 67m, 95m, 58m, 78m) passes Class 10's six marks, and C# collects them into an array. The result, 79.17, matches Lesson 33.

Example: normal parameters are copies​

AddGraceMarks(mark) adds 2 to its parameter, but the program still prints 67. The method received a copy of mark, changed the copy, and the copy was thrown away.

That is a feature, not a bug: calling a method can never surprise you by changing your variables, unless you see ref or out in the call.

Example: one-line methods and local functions​

HasPassed uses => instead of braces and return. This expression-bodied form suits methods that are one simple expression.

AddGraceMarks is written at the bottom of Program.cs, outside any class. That makes it a local function: a method only this file can use. Use local functions for small helpers that belong to one place, and a shared class for rules the whole app needs.

Common mistakes​

  1. Forgetting ref in the call. ApplyPayment(balance, 10000m) fails with error CS1620: Argument 1 must be passed with the 'ref' keyword. Fix: write ref in both places, or return the new value instead.
  2. Passing the wrong type. CalculatePercentage("67") fails with error CS1503: Argument 1: cannot convert from 'string' to 'decimal'. Fix: convert first, with TryParse.
  3. Expecting a method to change a normal parameter. Fix: return the new value.
  4. Methods that do several jobs. A method that calculates, prints, and saves is hard to reuse. Fix: one job per method; let the caller print.
  5. Copying a rule instead of calling the method. Two copies drift apart. Fix: keep the rule in one method.

In real projects​

From 16 years of .NET projects

On one school project, the "total marks" calculation existed in four places: the report card, the results screen, the PDF export, and a text message to parents. When the school added grace marks, three were updated. Parents received a text with a different total from the report card.

We moved the calculation into one method with tests, and every screen called it. The next rule change took one edit.

My review question for any duplicated logic: "If this rule changes tomorrow, how many files do I touch?" The answer should be one.

Best practices​

  • Give each method one job, and a verb name such as GetGrade or CalculateFine.
  • Return values instead of using ref, unless there is a strong reason.
  • Use the Try pattern for operations that can fail with normal input.
  • Use optional parameters for sensible defaults, and named arguments for clarity.
  • Keep calculation methods free of Console calls, so any screen can use them.

Recap​

  • A method has a return type, a name, parameters, and a body.
  • Optional parameters have defaults; named arguments make calls readable.
  • Overloads share a name but differ in parameters.
  • Normal parameters are copies; ref and out give the method the caller's variable.
  • The Try pattern returns success and hands results back through out.
  • params accepts any number of arguments; => writes one-line methods.

Homework​

Library module task: Write two methods. GetDueDate takes an issue date and an optional loan period of 14 days. CalculateFine takes the due date, the return date, and an optional fine per day of 2; it returns 0 when the book is on time.

Call them for a book issued on 10 July, returned on 20 July, and returned on 28 July. Then call CalculateFine again with a staff rate of 5 per day, using a named argument.

A correct result looks like:

Output
Issued 2026-07-10, due 2026-07-24
Returned 2026-07-20: fine 0
Returned 2026-07-28: fine 8
Staff copy, 5 per day: fine 20
Show the solution
library-assignment/034-library-methods.cs
// Library homework, Lesson 34: library rules as methods with optional parameters.
// Run with: dotnet run library-assignment/034-library-methods.cs
DateOnly issuedOn = new DateOnly(2026, 7, 10);
DateOnly dueOn = GetDueDate(issuedOn);

Console.WriteLine($"Issued {issuedOn:yyyy-MM-dd}, due {dueOn:yyyy-MM-dd}");
Console.WriteLine($"Returned 2026-07-20: fine {CalculateFine(dueOn, new DateOnly(2026, 7, 20))}");
Console.WriteLine($"Returned 2026-07-28: fine {CalculateFine(dueOn, new DateOnly(2026, 7, 28))}");
Console.WriteLine($"Staff copy, 5 per day: fine {CalculateFine(dueOn, new DateOnly(2026, 7, 28), finePerDay: 5m)}");

static DateOnly GetDueDate(DateOnly issuedOn, int loanDays = 14) => issuedOn.AddDays(loanDays);

static decimal CalculateFine(DateOnly dueOn, DateOnly returnedOn, decimal finePerDay = 2m)
{
int daysLate = returnedOn.DayNumber - dueOn.DayNumber;
if (daysLate <= 0)
{
return 0;
}
return daysLate * finePerDay;
}

GetDueDate uses => because it is one expression. CalculateFine needs an if, so it uses a normal body.

Quiz​

  1. What does the return type void mean?

    1. The method returns null
    2. The method returns nothing
    3. The method is empty
    4. The method cannot be called
    Show answer

    Answer: B. The method returns nothing void methods do work but give no value back.

  2. CalculatePercentage(decimal marksObtained, decimal maxMarks = 100m) is called as CalculatePercentage(67m). What is maxMarks?

    1. 0
    2. 67
    3. 100
    4. An error
    Show answer

    Answer: C. 100 An optional parameter takes its default when the caller leaves it out.

  3. Which keyword means the method must give a value back through the parameter?

    1. ref
    2. out
    3. params
    4. static
    Show answer

    Answer: B. out An out parameter must be set by the method before it returns.

  4. A method adds 2 to a normal decimal parameter. What happens to the caller's variable?

    1. It increases by 2
    2. It stays the same
    3. It becomes 0
    4. The program fails
    Show answer

    Answer: B. It stays the same Normal parameters receive a copy.

  5. What is method overloading?

    1. A method that does too much
    2. Several methods with the same name and different parameters
    3. Calling a method twice
    4. A method with optional parameters
    Show answer

    Answer: B. Several methods with the same name and different parameters The compiler chooses the overload from the argument types.

Interview questions​

Questions are grouped by experience level. Click a question to see a model answer. All questions: C# interview questions.

Fresher (0-2 yrs)​

F What is the difference between a parameter and an argument? concept

A parameter is the named input in the method's definition, such as decimal maxMarks. An argument is the actual value passed in a call, such as 50m.

See the example · Permalink

F What is an optional parameter? concept

A parameter with a default value, such as decimal maxMarks = 100m. Callers can leave it out, and the default is used. Optional parameters must come after the required ones.

See the example · Permalink

F What is method overloading? concept

Several methods with the same name but different parameter lists, such as CalculateAttendancePercentage(int, int) and CalculateAttendancePercentage(string). The compiler picks one from the argument types. A different return type alone is not enough.

See the example · Permalink

Mid (3-5 yrs)​

M What is the difference between ref and out? concept

Both pass the caller's own variable. With ref, the variable must be assigned before the call, and the method may read and change it. With out, it need not be assigned before, and the method must assign it before returning. Both keywords appear in the definition and the call.

See the example · Permalink

M What is the Try pattern? concept

A method named TryX that returns true or false for success and hands back the result through an out parameter, like int.TryParse. It handles expected failures, such as bad input, without exceptions.

See the example · Permalink

M What does the params keyword do? concept

It lets callers pass any number of arguments, which C# collects into an array (or another collection type), such as Average(92m, 85m, 67m). It must be the last parameter.

See the example · Permalink

Senior (6-9 yrs)​

S A method takes nine parameters. What do you suggest in review? scenario

Too many parameters usually mean the method does too much or the values belong together. Split it into smaller methods, or group related values into an object, such as a request class with properties. Named arguments help readability in the meantime.

How to answer: Name the smell, explain the risks (wrong order of arguments, hard to test), and offer refactorings: parameter objects, smaller methods, sensible defaults.

See the example · Permalink

S The same calculation exists in four screens and they disagree after a rule change. How do you fix and prevent it? scenario

Move the calculation into one well-named method with tests at its boundaries, and make every screen call it. Remove the copies. In reviews, treat duplicated business logic as a defect.

How to answer: Cover the immediate fix (one method, tests), the data fix (correct anything produced wrongly), and prevention (shared library, review rule).

See the example · Permalink

Architect / Lead (10+ yrs)​

A When should an operation return a bool with out parameters, and when should it throw an exception? design

Use the Try pattern when failure is expected in normal use, such as parsing what a user typed. Throw an exception when failure means something is truly wrong, such as a missing database record that must exist. Many APIs offer both, like int.Parse and int.TryParse.

How to answer: Tie the choice to how often failure happens and who handles it. Mention the cost of exceptions and the clarity of result types in larger systems.

See the example · Permalink

Resources​

  • Code for this lesson: git tag lesson-34-methods (code repository coming soon)
  • Files: src/SchoolApp.Console/SchoolRules.cs, src/SchoolApp.Console/Program.cs, library-assignment/034-library-methods.cs
  • Video: coming soon

What's next​

In SchoolDb, many values can be missing: a mark for an absent student, a guardian's email. Next: how C# represents "no value", and how to avoid the most common crash in .NET. Lesson 35: Nullable types

Want live sessions, code reviews, and interview preparation? Join the next batch at Sahasra Technologies.