05. 메서드
코드 묶음에 이름을 붙여 재사용하는 단위가 메서드입니다. 매개변수·반환값·오버로딩·ref/out/in·params·식 본문 메서드까지 살펴봅니다.
이 강의에서 배우는 것
- 1메서드 선언·호출 흐름을 이해한다
- 2매개변수와 반환 타입을 명시할 수 있다
- 3**오버로딩(overloading)** 의 의미와 규칙을 안다
- 4`out` / `ref` / `in` 키워드의 용도를 구분한다
- 5선택적(optional) 매개변수와 명명된(named) 인수를 사용한다
소개
같은 일을 여러 곳에서 한다면 **메서드(method)** 로 묶어 두는 게 답입니다. 메서드는 이름·매개변수·반환값으로 정의되며, 같은 이름을 다른 시그니처로 여러 개 둘 수도 있습니다(오버로딩).
핵심 개념
1) 메서드 선언과 호출
static int Add(int a, int b) // 반환 타입 매개변수
{
return a + b;
}
int s = Add(3, 4); // 호출 → 7- 반환값이 없으면 `void`
- top-level statements 안에서 `static` 으로 선언하면 같은 파일에서 호출 가능
2) 오버로딩
**같은 이름** 의 메서드를 **시그니처(매개변수 개수/타입)** 만 다르게 여러 개 둘 수 있습니다. 반환 타입만 다른 건 오버로딩이 아닙니다.
static int Add(int a, int b) => a + b;
static double Add(double a, double b) => a + b;
int i = Add(1, 2); // int 버전
double d = Add(1.5, 2.5); // double 버전3) `out` / `ref` / `in`
| 키워드 | 들어갈 때 | 나올 때 | 용도 |
|---|---|---|---|
| `out` | 초기화 불필요 | 메서드가 **반드시 대입** | "두 번째 반환값" |
| `ref` | 초기화 필수 | 양방향 | 호출자 값을 직접 수정 |
| `in` | 초기화 필수 | 읽기 전용 | 큰 struct 의 복사 비용 절감 |
if (int.TryParse("42", out int n)) // out
Console.WriteLine(n);
static void Swap(ref int a, ref int b) { (a, b) = (b, a); } // ref
static void Print(in Point p) { Console.WriteLine($"{p.X},{p.Y}"); } // in호출 시 `out`/`ref` 는 키워드를 다시 써 줘야 합니다: `Swap(ref x, ref y);`.
4) 선택적(optional) 매개변수
끝쪽 매개변수에 기본값을 주면 생략 가능합니다.
static void Greet(string name, string lang = "ko")
{
Console.WriteLine(lang == "ko" ? $"안녕, {name}!" : $"Hi, {name}!");
}
Greet("지수"); // 안녕, 지수!
Greet("Jisoo", "en"); // Hi, Jisoo!5) 명명된(named) 인수
인수를 **이름으로 지정** 하면 순서와 무관하게 전달 가능. 가독성·기본값 건너뛰기에 유용.
static void Order(string item, int qty = 1, bool gift = false) { /* ... */ }
Order(item: "사과", gift: true); // qty 는 기본값 1, gift 만 명시핵심 예제
예제 1 — `Basic/Program.cs` : 메서드로 분리해 호출
static int Add(int a, int b)
{
return a + b;
}
int x = Add(3, 4);
int y = Add(10, 20);
Console.WriteLine($"Add(3, 4) = {x}");
Console.WriteLine($"Add(10,20) = {y}");**실행 결과**
Add(3, 4) = 7
Add(10,20) = 30**메모:** top-level statements 파일에서는 메서드를 **파일 마지막 쪽에 모아 두는 것이 일반적**입니다. (현재 예제는 짧아 위쪽에 뒀습니다.)
예제 2 — `Overloading/Program.cs` : 같은 이름, 다른 시그니처
Console.WriteLine($"Calc.Add(1, 2) = {Calc.Add(1, 2)}");
Console.WriteLine($"Calc.Add(1.5, 2.5) = {Calc.Add(1.5, 2.5)}");
static class Calc
{
public static int Add(int a, int b) => a + b;
public static double Add(double a, double b) => a + b;
}**실행 결과**
Calc.Add(1, 2) = 3
Calc.Add(1.5, 2.5) = 4**메모:** `=>` 는 **expression-bodied member** — 본문이 한 줄이면 깔끔하게 쓸 수 있습니다. 로컬 함수는 오버로딩이 안 되므로 같은 이름을 쓰려면 `static class` 로 감싸야 합니다.
예제 3 — `OutRefIn/Program.cs` : `out`, `ref`, `in` 시연
namespace CodingNow.Lecture.Basics05;
internal static class Program
{
public static void Main()
{
// out: TryParse 패턴
if (int.TryParse("123", out int parsed))
Console.WriteLine($"파싱 성공: {parsed}");
// ref: 두 값 교환
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"Swap 후 x={x}, y={y}");
// in: 읽기 전용 참조 전달
Point p = new(10, 20);
Print(in p);
}
static void Swap(ref int a, ref int b)
{
(a, b) = (b, a);
}
static void Print(in Point p)
{
// p.X = 0; // 컴파일 에러: in 은 읽기 전용
Console.WriteLine($"Point({p.X}, {p.Y})");
}
}
internal readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y) { X = x; Y = y; }
}**실행 결과**
파싱 성공: 123
Swap 후 x=2, y=1
Point(10, 20)**메모:** `out int parsed` 는 호출 자리에서 **변수를 동시에 선언** 합니다. C# 7+ 기능.
예제 4 — `OptionalNamed/Program.cs` : 선택적·명명된 인수
static void Order(string item, int qty = 1, bool gift = false)
{
string wrap = gift ? " (선물 포장)" : "";
Console.WriteLine($"{item} × {qty}{wrap}");
}
Order("사과"); // 기본값
Order("배", 3); // qty 지정
Order(item: "포도", gift: true); // 이름으로 — qty 는 기본 1
Order("귤", qty: 5, gift: true);**실행 결과**
사과 × 1
배 × 3
포도 × 1 (선물 포장)
귤 × 5 (선물 포장)**메모:** 선택적 매개변수는 **항상 끝쪽** 에 와야 합니다. 가운데에 두려면 호출자가 명명된 인수로 모두 지정해야 해 불편합니다.
전체 예제 코드 (src/)
src/Basic/Basic.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Basics05</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
src/Basic/Program.cs
// 메서드로 분리해 호출
static int Add(int a, int b)
{
return a + b;
}
int x = Add(3, 4);
int y = Add(10, 20);
Console.WriteLine($"Add(3, 4) = {x}");
Console.WriteLine($"Add(10,20) = {y}");
src/OptionalNamed/OptionalNamed.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Basics05</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
src/OptionalNamed/Program.cs
// 선택적 매개변수 + 명명된 인수
static void Order(string item, int qty = 1, bool gift = false)
{
string wrap = gift ? " (선물 포장)" : "";
Console.WriteLine($"{item} × {qty}{wrap}");
}
Order("사과"); // 기본값
Order("배", 3); // qty 지정
Order(item: "포도", gift: true); // 이름으로
Order("귤", qty: 5, gift: true);
src/OutRefIn/OutRefIn.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Basics05</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
src/OutRefIn/Program.cs
namespace CodingNow.Lecture.Basics05;
internal static class Program
{
public static void Main()
{
// out: TryParse 패턴
if (int.TryParse("123", out int parsed))
Console.WriteLine($"파싱 성공: {parsed}");
// ref: 두 값 교환
int x = 1, y = 2;
Swap(ref x, ref y);
Console.WriteLine($"Swap 후 x={x}, y={y}");
// in: 읽기 전용 참조 전달
Point p = new(10, 20);
Print(in p);
}
static void Swap(ref int a, ref int b)
{
(a, b) = (b, a);
}
static void Print(in Point p)
{
// p.X = 0; // 컴파일 에러: in 은 읽기 전용
Console.WriteLine($"Point({p.X}, {p.Y})");
}
}
internal readonly struct Point
{
public int X { get; }
public int Y { get; }
public Point(int x, int y)
{
X = x;
Y = y;
}
}
src/Overloading/Overloading.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Basics05</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
src/Overloading/Program.cs
// 메서드 오버로딩 - 같은 이름, 다른 시그니처
// 로컬 함수는 오버로딩이 안 되므로 static 클래스로 감싸 보여 준다.
Console.WriteLine($"Calc.Add(1, 2) = {Calc.Add(1, 2)}");
Console.WriteLine($"Calc.Add(1.5, 2.5) = {Calc.Add(1.5, 2.5)}");
static class Calc
{
public static int Add(int a, int b) => a + b;
public static double Add(double a, double b) => a + b;
}
자주 하는 실수
- 메서드 이름은 같지만 **반환 타입만 다르게** 정의 — 컴파일 에러. 시그니처에 반환 타입은 포함되지 않습니다.
- `out` 매개변수에 메서드 안에서 **값을 안 주고** 끝냄 — 컴파일 에러.
- 호출 시 `ref`/`out` 키워드 누락 — `Swap(x, y)` 가 아니라 `Swap(ref x, ref y)`.
- 선택적 매개변수의 **기본값을 가변 객체** 로 — 컴파일 타임 상수만 허용.
- 너무 긴 매개변수 목록 — 4~5 개를 넘으면 객체로 묶는 걸 고려.
정리
- 메서드는 입력(매개변수) → 처리 → 출력(반환값) 의 단위다
- 오버로딩은 **시그니처가 달라야** 성립
- `out` 은 "추가 반환", `ref` 는 "양방향", `in` 은 "읽기 전용 참조"
- 선택적·명명된 인수로 호출 코드를 깔끔하게 유지
과제
**과제 - 05. 메서드**
문제 1 — 소수(prime) 출력기
- 프로젝트 폴더: `Homework01/`
- 핵심 개념: 메서드 선언, `for`, 반환값
요구사항
- `static bool IsPrime(int n)` 메서드를 직접 만든다
- 1 이하: `false`
- 2: `true`
- 그 외: `2 ~ √n` 으로 나눠 떨어지면 `false`
- `for` 로 1 ~ 50 사이 소수를 한 줄에 모두 출력한다 (공백 구분)
예상 출력
1 ~ 50 의 소수:
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47힌트
- `Math.Sqrt(n)` 으로 제곱근을 구하고 `(int)` 로 캐스팅
- `for (int i = 2; i * i <= n; i++)` 처럼 곱셈으로 비교해도 OK
문제 2 — `TryDivide` 메서드 (`out` 패턴)
- 프로젝트 폴더: `Homework02/`
- 핵심 개념: `out` 매개변수, 반환값 `bool`
요구사항
- `static bool TryDivide(int a, int b, out int result)` 메서드를 만든다
- `b == 0` 이면 `result = 0;` 후 `false` 반환
- 아니면 `result = a / b;` 후 `true` 반환
- 두 정수를 입력받아 호출하고, 성공/실패 메시지를 출력한다
예상 출력 (성공)
a: 10
b: 3
10 / 3 = 3예상 출력 (실패)
a: 10
b: 0
0 으로 나눌 수 없습니다.힌트
- `out` 매개변수는 메서드의 모든 경로에서 반드시 값을 대입해야 한다
- 호출은 `if (TryDivide(a, b, out int r)) { ... }` 형태
정답 확인
직접 풀어 본 후 [`answer/`](./answer/) 폴더의 정답과 비교해 보세요.
정답 (answer/)
homework/answer/Homework01/Homework01.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Basics05</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
homework/answer/Homework01/Program.cs
// 1~50 의 소수 출력
static bool IsPrime(int n)
{
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2)
{
if (n % i == 0) return false;
}
return true;
}
Console.WriteLine("1 ~ 50 의 소수:");
for (int i = 1; i <= 50; i++)
{
if (IsPrime(i))
Console.Write($"{i} ");
}
Console.WriteLine();
homework/answer/Homework02/Homework02.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CodingNow.Lecture.Basics05</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
homework/answer/Homework02/Program.cs
// TryDivide - out 패턴
static bool TryDivide(int a, int b, out int result)
{
if (b == 0)
{
result = 0;
return false;
}
result = a / b;
return true;
}
Console.Write("a: ");
int a = int.Parse(Console.ReadLine()!);
Console.Write("b: ");
int b = int.Parse(Console.ReadLine()!);
if (TryDivide(a, b, out int r))
Console.WriteLine($"{a} / {b} = {r}");
else
Console.WriteLine("0 으로 나눌 수 없습니다.");
직접 해 보기
cd src/Basic && dotnet run
cd ../Overloading && dotnet run
cd ../OutRefIn && dotnet run
cd ../OptionalNamed && dotnet run다음 단원
[../../02_객체지향/06_클래스와_객체](../../02_객체지향/06_클래스와_객체/) — 데이터와 동작을 묶는 클래스로 한 단계 도약합니다.