Methods and Parameters: Reusable School Rules – C# | Lesson 34
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, andoutparameters. - 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
public static decimal CalculatePercentage(decimal marksObtained, decimal maxMarks = 100m)
{
return Math.Round(marksObtained / maxMarks * 100, 2);
}
| Part | Meaning |
|---|---|
public static | Who can call it, and how; Lessons 37 and 42 explain both words |
decimal | The return type: what the method gives back; void means nothing |
CalculatePercentage | The 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:
| Call | Which 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
| Kind | Keyword | What the method receives | Use for |
|---|---|---|---|
| Normal | none | A copy of the value | Almost everything |
| By reference | ref | The caller's own variable, to read and change | Rare: changing a value in place |
| Output | out | The caller's variable, which the method must fill | Returning 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
// 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
// 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;
}
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
- Forgetting
refin the call.ApplyPayment(balance, 10000m)fails witherror CS1620: Argument 1 must be passed with the 'ref' keyword. Fix: writerefin both places, or return the new value instead. - Passing the wrong type.
CalculatePercentage("67")fails witherror CS1503: Argument 1: cannot convert from 'string' to 'decimal'. Fix: convert first, withTryParse. - Expecting a method to change a normal parameter. Fix: return the new value.
- 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.
- Copying a rule instead of calling the method. Two copies drift apart. Fix: keep the rule in one method.
In real 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
GetGradeorCalculateFine. - 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
Consolecalls, 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;
refandoutgive the method the caller's variable. - The Try pattern returns success and hands results back through
out. paramsaccepts 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:
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 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
What does the return type void mean?
Show answer
Answer: B. The method returns nothing void methods do work but give no value back.
CalculatePercentage(decimal marksObtained, decimal maxMarks = 100m) is called as CalculatePercentage(67m). What is maxMarks?
Show answer
Answer: C. 100 An optional parameter takes its default when the caller leaves it out.
Which keyword means the method must give a value back through the parameter?
Show answer
Answer: B. out An out parameter must be set by the method before it returns.
A method adds 2 to a normal decimal parameter. What happens to the caller's variable?
Show answer
Answer: B. It stays the same Normal parameters receive a copy.
What is method overloading?
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.