← C# 강의 목록으로
🟣
기초
기초 · 선수: 이전 단원

05. 메서드

코드 묶음에 이름을 붙여 재사용하는 단위가 메서드입니다. 매개변수·반환값·오버로딩·ref/out/in·params·식 본문 메서드까지 살펴봅니다.

C#.NET 8기초메서드
소요 시간
약 1~1.5시간
난이도
📊 초급
선수 조건
🎯 이전 단원 또는 동등 지식
결과물
코드 묶음에 이름을 붙여 재사용하는 단위가 메서드입니다. 매개변수·반환값·오버로딩·ref/out/in·params·식 본문 메서드까지 살펴봅니다.

이 강의에서 배우는 것

  • 1메서드 선언·호출 흐름을 이해한다
  • 2매개변수와 반환 타입을 명시할 수 있다
  • 3**오버로딩(overloading)** 의 의미와 규칙을 안다
  • 4`out` / `ref` / `in` 키워드의 용도를 구분한다
  • 5선택적(optional) 매개변수와 명명된(named) 인수를 사용한다

소개

같은 일을 여러 곳에서 한다면 **메서드(method)** 로 묶어 두는 게 답입니다. 메서드는 이름·매개변수·반환값으로 정의되며, 같은 이름을 다른 시그니처로 여러 개 둘 수도 있습니다(오버로딩).

핵심 개념

1) 메서드 선언과 호출

csharp
static int Add(int a, int b)   // 반환 타입 매개변수
{
    return a + b;
}

int s = Add(3, 4);             // 호출 → 7
  • 반환값이 없으면 `void`
  • top-level statements 안에서 `static` 으로 선언하면 같은 파일에서 호출 가능

2) 오버로딩

**같은 이름** 의 메서드를 **시그니처(매개변수 개수/타입)** 만 다르게 여러 개 둘 수 있습니다. 반환 타입만 다른 건 오버로딩이 아닙니다.

csharp
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 의 복사 비용 절감
csharp
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) 매개변수

끝쪽 매개변수에 기본값을 주면 생략 가능합니다.

csharp
static void Greet(string name, string lang = "ko")
{
    Console.WriteLine(lang == "ko" ? $"안녕, {name}!" : $"Hi, {name}!");
}

Greet("지수");           // 안녕, 지수!
Greet("Jisoo", "en");    // Hi, Jisoo!

5) 명명된(named) 인수

인수를 **이름으로 지정** 하면 순서와 무관하게 전달 가능. 가독성·기본값 건너뛰기에 유용.

csharp
static void Order(string item, int qty = 1, bool gift = false) { /* ... */ }

Order(item: "사과", gift: true);    // qty 는 기본값 1, gift 만 명시

핵심 예제

예제 1 — `Basic/Program.cs` : 메서드로 분리해 호출

csharp
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}");

**실행 결과**

text
Add(3, 4)  = 7
Add(10,20) = 30

**메모:** top-level statements 파일에서는 메서드를 **파일 마지막 쪽에 모아 두는 것이 일반적**입니다. (현재 예제는 짧아 위쪽에 뒀습니다.)

예제 2 — `Overloading/Program.cs` : 같은 이름, 다른 시그니처

csharp
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;
}

**실행 결과**

text
Calc.Add(1, 2)       = 3
Calc.Add(1.5, 2.5)   = 4

**메모:** `=>` 는 **expression-bodied member** — 본문이 한 줄이면 깔끔하게 쓸 수 있습니다. 로컬 함수는 오버로딩이 안 되므로 같은 이름을 쓰려면 `static class` 로 감싸야 합니다.

예제 3 — `OutRefIn/Program.cs` : `out`, `ref`, `in` 시연

csharp
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; }
}

**실행 결과**

text
파싱 성공: 123
Swap 후 x=2, y=1
Point(10, 20)

**메모:** `out int parsed` 는 호출 자리에서 **변수를 동시에 선언** 합니다. C# 7+ 기능.

예제 4 — `OptionalNamed/Program.cs` : 선택적·명명된 인수

csharp
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);

**실행 결과**

text
사과 × 1
배 × 3
포도 × 1 (선물 포장)
귤 × 5 (선물 포장)

**메모:** 선택적 매개변수는 **항상 끝쪽** 에 와야 합니다. 가운데에 두려면 호출자가 명명된 인수로 모두 지정해야 해 불편합니다.

전체 예제 코드 (src/)

src/Basic/Basic.csproj

xml
<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

csharp
// 메서드로 분리해 호출
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

xml
<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

csharp
// 선택적 매개변수 + 명명된 인수
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

xml
<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

csharp
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

xml
<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

csharp
// 메서드 오버로딩 - 같은 이름, 다른 시그니처
// 로컬 함수는 오버로딩이 안 되므로 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;
}

자주 하는 실수

  1. 메서드 이름은 같지만 **반환 타입만 다르게** 정의 — 컴파일 에러. 시그니처에 반환 타입은 포함되지 않습니다.
  2. `out` 매개변수에 메서드 안에서 **값을 안 주고** 끝냄 — 컴파일 에러.
  3. 호출 시 `ref`/`out` 키워드 누락 — `Swap(x, y)` 가 아니라 `Swap(ref x, ref y)`.
  4. 선택적 매개변수의 **기본값을 가변 객체** 로 — 컴파일 타임 상수만 허용.
  5. 너무 긴 매개변수 목록 — 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 사이 소수를 한 줄에 모두 출력한다 (공백 구분)

예상 출력

text
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` 반환
  • 두 정수를 입력받아 호출하고, 성공/실패 메시지를 출력한다

예상 출력 (성공)

text
a: 10
b: 3
10 / 3 = 3

예상 출력 (실패)

text
a: 10
b: 0
0 으로 나눌 수 없습니다.

힌트

  • `out` 매개변수는 메서드의 모든 경로에서 반드시 값을 대입해야 한다
  • 호출은 `if (TryDivide(a, b, out int r)) { ... }` 형태

정답 확인

직접 풀어 본 후 [`answer/`](./answer/) 폴더의 정답과 비교해 보세요.

정답 (answer/)

homework/answer/Homework01/Homework01.csproj

xml
<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

csharp
// 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

xml
<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

csharp
// 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 으로 나눌 수 없습니다.");

직접 해 보기

bash
cd src/Basic && dotnet run
cd ../Overloading && dotnet run
cd ../OutRefIn && dotnet run
cd ../OptionalNamed && dotnet run

다음 단원

[../../02_객체지향/06_클래스와_객체](../../02_객체지향/06_클래스와_객체/) — 데이터와 동작을 묶는 클래스로 한 단계 도약합니다.

예제 코드 / 강의 자료

전체 강의 자료와 예제 코드는 GitHub에서 자유롭게 받아볼 수 있습니다.

GitHub에서 보기 ↗