Object-oriented

Object-oriented

oop
Published

April 6, 2025

Abstract

C#의 메소드는 코드 재사용성, 모듈화, 유지 보수 용이성 및 가독성 향상에 핵심적인 기능을 제공하며, 특정 작업을 수행하는 코드 블록입니다. return을 통해 값 반환 및 실행 흐름 제어가 가능하고, 필수, 선택적, 가변 개수 매개변수를 통해 다양한 입력 방식을 지원합니다. 특히 ref, out 키워드를 사용한 참조 기반 매개변수 전달 및 반환은 값 형식 데이터 처리 효율성을 높입니다. 메소드 오버로딩은 이름 재사용으로 API 사용성을 개선하며, 명명된 인수와 선택적 인수는 메소드 호출 유연성을 극대화합니다. 더불어 로컬 함수는 메소드 내 작은 기능 캡슐화 및 코드 가독성을 높여 C# 프로그래밍의 효율성과 품질을 향상시키는 데 중요한 역할을 합니다.

메소드(Method)

메소드(Method)는 특정 작업을 수행하는 일련의 코드 명령문들을 묶어 놓은 것입니다. 마치 레고 블록처럼 코드를 기능별로 나누어 재사용할 수 있도록 돕고, 프로그램의 복잡성을 줄여줍니다.

한정자 반환_형식 메소드_이름(매개변수_목록)
{
    // 메소드 본문(실행될 코드)
    // return 반환값; // 반환 형식이 void가 아닌 경우
}
  • 한정자(Modifiers): 메소드의 접근 수준(public, private 등), 동작 방식(static, virtual 등)을 정의합니다. 생략 가능하며, 기본적으로 private 한정자가 적용됩니다.
  • 반환 형식(Return Type): 메소드가 작업을 완료하고 호출자에게 돌려주는 값의 데이터 형식을 지정합니다. 반환 값이 없는 경우 void 키워드를 사용합니다.
  • 메소드 이름(Method Name): 메소드를 호출할 때 사용하는 이름입니다. 명명 규칙을 따라야 하며, 동사로 시작하는 것이 일반적입니다.
  • 매개변수 목록(Parameter List): 메소드 호출 시 전달되는 입력 값들을 정의합니다. 쉼표로 구분하며, 각 매개변수는 형식 매개변수_이름 형태로 선언합니다. 매개변수가 없을 수도 있습니다.
  • 메소드 본문(Method Body): 실제 메소드가 수행하는 코드를 작성하는 부분입니다. 중괄호 {} 안에 위치합니다.
  • 반환값(Return Value): 메소드가 작업을 완료하고 호출자에게 반환하는 값입니다. return 키워드를 사용하여 반환하며, 반환 형식이 void가 아닌 경우 반드시 반환해야 합니다.

메소드 사용의 장점은 아래와 같습니다.

  • 코드 재사용성 증가: 동일한 코드를 여러 번 작성할 필요 없이, 메소드를 호출하여 반복 작업을 효율적으로 처리할 수 있습니다.
  • 모듈화 및 구조화: 프로그램을 기능별로 분리하여 코드를 체계적으로 구성하고, 복잡한 코드를 단순화할 수 있습니다.
  • 유지 보수 용이성 향상: 코드 수정 및 디버깅 시 메소드 단위로 작업이 가능하여 효율적인 유지 보수가 가능합니다.
  • 가독성 향상: 코드의 논리적인 흐름을 명확하게 파악할 수 있도록 도와줍니다.

예시: 두 개의 정수를 더하는 메소드

public static int Add(int a, int b)
{
    int sum = a + b;
    return sum;
}

// 메소드 호출
int result = Methods.Add(5, 3);
Console.WriteLine($"Methods.Add Result is {result}");

return에 대하여

return 키워드는 메소드의 실행을 종료하고, 값을 반환하거나, 메소드 실행 흐름을 제어하는 데 사용됩니다.

return 키워드는 크게 3가지 역할을 합니다.

  1. 메소드 실행 종료: return 문을 만나면 메소드 실행이 즉시 중단됩니다. return 문 이후의 코드는 실행되지 않습니다.
  2. 값 반환: 메소드의 반환 형식이 void가 아닌 경우, return 키워드 뒤에 반환할 값을 명시합니다. 반환 값은 메소드의 반환 형식과 일치해야 합니다.
  3. 메소드 실행 흐름 제어: 조건문이나 반복문 안에서 return을 사용하여 특정 조건에서 메소드를 종료하고 호출자로 되돌아갈 수 있습니다.

몇가지 주의 사항이 있는데, 아래와 같습니다.

  • 반환 형식이 void가 아닌 메소드는 반드시 값을 반환해야 합니다. 그렇지 않으면 컴파일 오류가 발생합니다.
  • return 키워드는 메소드 본문 내에서 여러 번 사용할 수 있지만, 하나의 return 문만 실행됩니다. 실행 흐름이 return 문에 도달하면 메소드는 즉시 종료됩니다.

return을 사용한 대표적인 값 반환에 대한 예제는 아래와 같습니다.

  • 값 반환:
public static int Multiply(int a, int b)
{
    return a * b; // 곱셈 결과를 반환하고 메소드 종료
}
  • 조건부 반환:
public static bool IsEven(int number)
{
    if (number % 2 == 0)
    {
        return true; // 짝수이면 true 반환하고 메소드 종료
    }
    return false; // 홀수이면 false 반환하고 메소드 종료
}
  • 메소드 중간 종료(void 메소드): void 반환 형식을 가진 메소드에서도 return 키워드를 사용하여 메소드 실행을 중간에 종료할 수 있습니다. 이 경우에는 반환 값을 명시하지 않습니다.
public static void PrintMessage(string message)
{
    if(string.IsNullOrEmpty(message))
    {
        Console.WriteLine("메시지가 없습니다.");
        return; // 메시지가 없으면 메소드 종료
    }
    Console.WriteLine($"메시지: {message}");
}

매개변수

매개변수(Parameter)는 메소드에 전달되는 입력 값으로, 메소드가 실행될 때 필요한 데이터를 외부로부터 받아들이는 역할을 합니다. 매개변수를 사용하면 메소드의 범용성을 높이고, 다양한 입력 값에 대해 동일한 작업을 수행할 수 있습니다.

매개변수의 종류는 아래와 같습니다.

  • 필수 매개변수(Required Parameters): 메소드를 호출할 때 반드시 값을 전달해야 하는 매개변수입니다. 메소드 선언 시 매개변수 목록에 형식과 이름을 지정하여 정의합니다.
public static void Greet(string name) // name은 필수 매개변수
{
    Console.WriteLine($"안녕하세요, {name}님!");
}
  • 선택적 매개변수(Optional Parameters): 메소드를 호출할 때 값을 전달하지 않아도 되는 매개변수입니다. 메소드 선언 시 매개변수에 기본값을 할당하여 정의합니다. 선택적 매개변수는 매개변수 목록의 가장 뒤쪽에 위치해야 합니다.
public static void CalculateArea(int width, int height = 10) // height는 선택적 매개변수, 기본값 10
{
   int area = width * height;
   Console.WriteLine($"넓이: {area}");
}

CalculateArea(5);    // height 매개변수 생략, 기본값 10 사용, 넓이: 50 출력
CalculateArea(5, 20);  // height 매개변수에 20 전달, 넓이: 100 출력
  • 매개변수 배열(Parameter Arrays 또는 가변 개수의 인수): 동일한 형식의 매개변수를 가변적으로 여러 개 전달할 수 있도록 하는 매개변수입니다. params 키워드를 사용하여 선언하며, 매개변수 목록의 가장 마지막에 위치해야 합니다.(6.8 가변 개수의 인수 챕터에서 자세히 다룹니다.)
public static int SumNumbers(params int[] numbers) // numbers는 매개변수 배열
{
    int sum = 0;
    foreach(int number in numbers)
    {
        sum += number;
    }
    return sum;
}

int sum1 = SumNumbers(1, 2, 3);    // 3개의 인수를 매개변수 배열로 전달
int sum2 = SumNumbers(10, 20, 30, 40); // 4개의 인수를 매개변수 배열로 전달

매개변수 전달 방식

  • 값에 의한 전달(Pass by Value): 기본적으로 매개변수는 값에 의해 전달됩니다. 메소드 호출 시 전달되는 인수의 값이 매개변수에 복사되어 메소드 내에서 사용됩니다. 메소드 내에서 매개변수 값을 변경해도 원본 인수의 값에는 영향을 미치지 않습니다.

  • 참조에 의한 전달(Pass by Reference): ref 키워드를 사용하여 매개변수를 참조에 의해 전달할 수 있습니다. 메소드 호출 시 전달되는 인수의 참조(메모리 주소)가 매개변수에 전달됩니다. 메소드 내에서 매개변수 값을 변경하면 원본 인수의 값도 함께 변경됩니다.

  • 출력 전용 매개변수(Out Parameters): out 키워드를 사용하여 메소드에서 값을 반환하는 용도로 사용하는 매개변수입니다. 메소드 호출 전에 초기화할 필요가 없으며, 메소드 내에서 반드시 값을 할당해야 합니다.

참조에 의한 매개변수 전달

C#에서 매개변수는 기본적으로 “값에 의한 전달(Pass by Value)” 방식으로 전달됩니다. 즉, 메소드를 호출할 때 인수의 “값”이 매개변수에 복사되어 전달됩니다. 따라서 메소드 내에서 매개변수 값을 변경하더라도, 호출 시 사용한 원래 변수의 값은 변하지 않습니다.

하지만 때로는 메소드 내에서 전달받은 인수의 값을 직접 변경해야 할 경우가 있습니다. 이러한 경우 “참조에 의한 전달(Pass by Reference)” 방식을 사용합니다. 참조에 의한 전달은 ref 키워드를 사용하여 구현합니다.

  • 메소드 선언 시: 매개변수 형식 앞에 ref 키워드를 추가합니다.
  • 메소드 호출 시: 인수를 전달할 때에도 ref 키워드를 함께 사용합니다.
public static void Swap(ref int a, ref int b) // ref 키워드를 사용하여 참조 매개변수 선언
{
    int temp = a;
    a = b;
    b = temp;
}

int x = 10;
int y = 20;

Console.WriteLine($"Before Swap: x = {x}, y = {y}"); // Before Swap: x = 10, y = 20
Swap(ref x, ref y); // ref 키워드를 사용하여 참조 인수 전달
Console.WriteLine($"After Swap: x = {x}, y = {y}"); // After Swap: x = 20, y = 10

참조에 의한 전달의 특징은 아래와 같습니다.

  • 원본 변수 값 변경: 메소드 내에서 참조 매개변수의 값을 변경하면, 메소드 호출 시 사용한 원본 변수의 값도 함께 변경됩니다. 위 예시에서 Swap 메소드 내에서 ab 값을 바꾸면 xy의 값도 실제로 변경되는 것을 확인할 수 있습니다.
  • 메모리 주소 공유: 값 복사 대신 변수의 메모리 주소를 공유하므로, 메소드와 호출자 간에 동일한 메모리 공간을 참조하게 됩니다.
  • 성능상의 이점: 값 형식 데이터의 크기가 큰 경우, 값 복사에 대한 성능 오버헤드를 줄일 수 있습니다.(하지만 일반적으로 값 형식 데이터는 크기가 작으므로, 성능 이점은 미미할 수 있습니다.)

참조 매개변수의 이점에도 불구하고 주의해야 할 사항이 있습니다.

  • 참조 매개변수는 메소드 호출 전에 반드시 초기화되어 있어야 합니다. 초기화되지 않은 변수를 ref 인수로 전달하면 컴파일 오류가 발생합니다.
  • ref 키워드는 값 형식 및 참조 형식 모두에 사용할 수 있습니다.
  • 참조에 의한 전달은 코드의 가독성을 떨어뜨릴 수 있으므로, 필요한 경우에만 신중하게 사용해야 합니다. 메소드 외부에서 변수의 값이 변경될 수 있다는 점을 명확히 인지하고 사용해야 합니다.

메소드의 결과를 참조로 반환하기

메소드는 값을 반환하는 것뿐만 아니라, “참조(reference)”를 반환할 수도 있습니다. 메소드가 참조를 반환한다는 것은, 메소드가 값을 직접 반환하는 대신, 변수 또는 객체의 메모리 주소를 반환한다는 의미입니다.

메소드에서 참조 반환 방식:

  1. 메소드 선언 시: 반환 형식 앞에 ref 키워드를 추가합니다.
  2. return 문: return 키워드 뒤에 참조를 반환할 변수를 명시합니다.
static int[] numbers = { 1, 2, 3, 4, 5 };

public static ref int GetNumberRef(int index) // ref 키워드를 사용하여 참조 반환 메소드 선언
{
    if(index < 0 || index >= numbers.Length)
    {
        throw new IndexOutOfRangeException("인덱스 범위를 벗어났습니다.");
    }
    return ref numbers[index]; // numbers 배열의 요소에 대한 참조 반환
}

public static void Main(string[] args)
{
    ref int numberRef = ref GetNumberRef(2); // ref 키워드를 사용하여 참조 반환값 받기
    Console.WriteLine($"Original value: {numberRef}"); // Original value: 3
    numberRef = 100; // 참조를 통해 원본 배열 요소 값 변경
    Console.WriteLine($"Modified value: {numberRef}"); // Modified value: 100
    Console.WriteLine($"Array value at index 2: {numbers[2]}"); // Array value at index 2: 100(원본 배열 값 변경 확인)
}

참조 반환의 특징은 아래와 같습니다.

  • 원본 데이터 직접 수정: 메소드로부터 반환받은 참조를 통해, 메소드 외부에서 원본 데이터(변수, 배열 요소, 객체 속성 등)를 직접 수정할 수 있습니다. 위 예시에서 numberRef를 통해 numbers 배열의 3번째 요소 값을 변경하는 것을 확인할 수 있습니다.
  • 별칭(Alias) 효과: 참조 반환 값을 받는 변수는 원본 데이터에 대한 또 다른 이름(별칭) 역할을 합니다. 참조 변수를 통해 데이터를 읽거나 수정하면, 원본 데이터에 직접 반영됩니다.
  • 메모리 효율성: 값을 복사하지 않고 참조를 전달하므로, 특히 크기가 큰 데이터의 경우 성능상의 이점을 얻을 수 있습니다.

앞선 참조를 활용한 매개변수와 마찬가지로 몇가지 주의사항이 있습니다.

  • 참조 반환 메소드는 반환하는 참조가 유효한 범위 내에 있도록 주의해야 합니다. 지역 변수에 대한 참조를 반환하는 것은 위험할 수 있습니다.(메소드 종료 시 지역 변수는 메모리에서 해제되므로, 반환된 참조는 유효하지 않게 됩니다.)
  • 참조 반환은 코드의 가독성을 떨어뜨릴 수 있으므로, 필요한 경우에만 신중하게 사용해야 합니다. 메소드 외부에서 데이터가 직접 변경될 수 있다는 점을 명확히 인지하고 사용해야 합니다.
  • 참조 반환은 주로 성능 최적화가 필요한 특정 시나리오(예: 대규모 데이터 구조 직접 조작)에서 활용됩니다. 일반적인 경우에는 값 반환 방식을 사용하는 것이 코드 이해 및 유지 보수에 더 유리할 수 있습니다.

출력 전용 매개변수

출력 전용 매개변수(Out Parameter)는 메소드에서 여러 개의 값을 “반환”하고 싶을 때 유용하게 사용되는 매개변수 형태입니다. 일반적인 return 문은 하나의 값만 반환할 수 있지만, 출력 매개변수를 사용하면 메소드 내에서 여러 개의 변수에 값을 할당하여 호출자에게 전달할 수 있습니다.

  • 메소드 선언 시: 매개변수 형식 앞에 out 키워드를 추가합니다.
  • 메소드 호출 시: 인수를 전달할 때 out 키워드를 함께 사용합니다.
  • 메소드 내에서 값 할당: 출력 매개변수는 메소드 내에서 반드시 값을 할당해야 합니다. 할당하지 않으면 컴파일 오류가 발생합니다.
  • 메소드 호출 후 값 사용: 메소드 호출 후, out 키워드를 사용하여 전달한 변수에 메소드에서 할당한 값이 저장됩니다.
public static void Divide(int numerator, int denominator, out int quotient, out int remainder) // out 키워드를 사용하여 출력 매개변수 선언
{
    quotient = numerator / denominator;
    remainder = numerator % denominator;
}

public static void Main(string[] args)
{
    int q, r; // 출력 매개변수로 사용할 변수 선언(초기화 불필요)
    Divide(10, 3, out q, out r); // out 키워드를 사용하여 출력 매개변수 전달
    Console.WriteLine($"몫: {q}, 나머지: {r}"); // 몫: 3, 나머지: 1(메소드에서 계산된 값이 변수에 할당됨)
}

출력 전용 매개변수의 특징은 아래와 같습니다.

  • 다중 값 반환: 하나의 메소드에서 여러 개의 결과 값을 반환할 수 있습니다. 위 예시에서 Divide 메소드는 몫과 나머지 두 개의 값을 출력 매개변수를 통해 반환합니다.
  • 메소드 호출 전 초기화 불필요: 출력 매개변수로 사용될 변수는 메소드 호출 전에 반드시 초기화될 필요는 없습니다. 메소드 내에서 값을 할당하는 것이 목적이기 때문입니다.
  • 메소드 내에서 값 할당 필수: 컴파일러는 메소드 내에서 출력 매개변수에 반드시 값이 할당되었는지 검사합니다. 값이 할당되지 않으면 컴파일 오류를 발생시켜 프로그래머의 실수를 방지합니다.

out 키워드 vs ref 키워드:

  • ref 키워드: 참조에 의한 매개변수 전달, 메소드 내에서 매개변수 값을 변경하면 원본 변수 값도 변경됨. 메소드 호출 전에 변수 초기화 필수.
  • out 키워드: 출력 전용 매개변수, 메소드에서 값을 반환하는 용도로 사용. 메소드 호출 전에 변수 초기화 불필요, 메소드 내에서 값 할당 필수.

refout 키워드를 사용하는 대표적인 사례를 살펴보겠습니다.

  • TryParse 메소드: 문자열을 특정 형식으로 변환 시도하고, 변환 성공 여부를 bool 값으로 반환하며, 변환된 값을 out 매개변수를 통해 전달합니다.(예: int.TryParse, DateTime.TryParse)
string input = "123";
int number;
bool success = int.TryParse(input, out number); // 변환 성공 여부를 success에, 변환된 값을 number에 저장

if(success)
{
    Console.WriteLine($"변환 성공: {number}");
}
else
{
    Console.WriteLine("변환 실패");
}
  • 다중 반환 값이 필요한 경우: 하나의 메소드에서 여러 개의 관련된 값을 함께 반환해야 할 때 out 매개변수를 활용하면 코드를 간결하게 유지할 수 있습니다.

메소드 오버로딩

메소드 오버로딩(Method Overloading)은 동일한 이름의 메소드를 여러 개 정의하는 것을 의미합니다. 오버로딩된 메소드들은 이름은 같지만, 매개변수의 개수나 형식, 순서가 다릅니다. 컴파일러는 메소드 호출 시 전달된 인수의 형식과 개수를 기반으로 적절한 오버로드된 메소드를 선택하여 실행합니다.

메소드 오버로딩의 조건은 아래와 같습니다.

  • 메소드 이름이 동일해야 합니다.
  • 매개변수 목록이 달라야 합니다. 다음 중 적어도 하나 이상이 달라야 오버로딩이 가능합니다.
    • 매개변수의 개수
    • 매개변수의 형식
    • 매개변수의 순서
public class Calculator
{
    // 1. 정수 덧셈 메소드
    public int Add(int a, int b)
    {
        return a + b;
    }

    // 2. 실수 덧셈 메소드(매개변수 형식이 다름)
    public double Add(double a, double b)
    {
        return a + b;
    }

    // 3. 세 개의 정수 덧셈 메소드(매개변수 개수가 다름)
    public int Add(int a, int b, int c)
    {
        return a + b + c;
    }

    // 4. 문자열 연결 메소드(매개변수 형식이 다름)
    public string Add(string str1, string str2)
    {
        return str1 + str2;
    }
}

public static void Main(string[] args)
{
    Calculator calculator = new Calculator();
    Console.WriteLine(calculator.Add(5, 10));     // 1. int Add(int a, int b) 호출
    Console.WriteLine(calculator.Add(3.14, 2.71));  // 2. double Add(double a, double b) 호출
    Console.WriteLine(calculator.Add(1, 2, 3));    // 3. int Add(int a, int b, int c) 호출
    Console.WriteLine(calculator.Add("Hello", "World")); // 4. string Add(string str1, string str2) 호출
}

메소드 오버로딩의 장점은 아래와 같습니다.

  • 메소드 이름 재사용: 동일한 기능을 수행하지만, 입력 데이터 형식이 다른 경우 메소드 이름을 재사용하여 코드의 일관성을 유지하고 가독성을 높일 수 있습니다. 위 예시에서 Add 메소드는 정수, 실수, 문자열 등 다양한 형식의 덧셈 연산을 모두 수행하지만, 동일한 이름으로 오버로딩되어 사용 편의성을 높입니다.
  • 사용자 편의성 향상: 메소드를 사용하는 개발자는 입력 데이터 형식에 맞춰 적절한 메소드를 선택하여 사용할 수 있습니다. 메소드 이름을 일관성 있게 유지하면서 다양한 입력 옵션을 제공하여 API 사용성을 향상시킵니다.
  • 코드 가독성 향상: 메소드 이름이 기능적으로 연관되어 있으므로, 코드의 의미를 파악하기 쉽고, 메소드 사용법을 쉽게 이해할 수 있습니다.

메소드 오버로딩의 주의사항은 아래와 같습니다.

  • 오버로드된 메소드들은 기능적으로 유사하거나 관련성이 있어야 합니다. 전혀 다른 기능을 수행하는 메소드를 동일한 이름으로 오버로드하는 것은 혼란을 야기할 수 있습니다.
  • 반환 형식만 다르게 하여 메소드를 오버로드하는 것은 불가능합니다. 매개변수 목록이 반드시 달라야 합니다.
  • 과도한 메소드 오버로딩은 오히려 코드의 복잡성을 증가시킬 수 있습니다. 적절한 수준에서 오버로딩을 활용하는 것이 중요합니다.

가변 개수의 인수

가변 개수의 인수(Variable Number of Arguments)는 메소드에 전달되는 인수의 개수가 정해지지 않고, 유연하게 변할 수 있도록 하는 기능입니다. C#에서는 params 키워드를 사용하여 매개변수 배열을 선언함으로써 가변 개수의 인수를 처리할 수 있습니다.

  • 메소드 선언 시: 매개변수 목록의 마지막 매개변수 앞에 params 키워드를 추가하고, 매개변수를 배열 형식으로 선언합니다.
  • 메소드 호출 시: params 매개변수에 0개 이상의 인수를 쉼표로 구분하여 전달합니다. 전달된 인수들은 배열로 묶여 메소드 내에서 사용됩니다.
public static int GetAverage(params int[] scores) // params 키워드를 사용하여 매개변수 배열 선언
{
    if(scores == null || scores.Length == 0)
    {
        return 0; // 인수가 없으면 0 반환
    }

    int sum = 0;
    foreach(int score in scores)
    {
        sum += score;
    }

    return sum / scores.Length;
}

public static void Main(string[] args)
{
    int average1 = GetAverage(90, 80, 70);    // 3개의 인수 전달
    int average2 = GetAverage(100, 95, 90, 85, 80); // 5개의 인수 전달
    int average3 = GetAverage();         // 0개의 인수 전달(빈 배열)

    Console.WriteLine($"Average 1: {average1}"); // Average 1: 80
    Console.WriteLine($"Average 2: {average2}"); // Average 2: 90
    Console.WriteLine($"Average 3: {average3}"); // Average 3: 0
}

가변 개수의 인수의 특징은 아래와 같습니다.

  • 유연한 인수 개수: 메소드를 호출할 때 인수의 개수를 자유롭게 조절할 수 있습니다. 위 예시에서 GetAverage 메소드는 0개, 3개, 5개 등 다양한 개수의 인수를 처리할 수 있습니다.
  • 배열로 처리: params 키워드로 선언된 매개변수는 메소드 내에서 배열로 취급됩니다. 따라서 foreach 문 등을 사용하여 배열 요소에 접근하고 반복 작업을 수행할 수 있습니다.
  • 단일 params 매개변수: 메소드 선언 시 params 키워드를 사용한 매개변수는 하나만 정의할 수 있으며, 매개변수 목록의 가장 마지막에 위치해야 합니다.

가변 개수의 인수의 활용 시나리오

  • 합계, 평균, 최대/최솟값 계산: 임의의 개수의 숫자들의 합계, 평균, 최대/최솟값을 계산하는 메소드를 구현할 때 유용합니다. 위 예시의 GetAverage 메소드가 이에 해당합니다.
  • 문자열 서식 지정: string.Format 메소드와 같이 서식 문자열과 가변적인 인수를 받아 문자열을 생성하는 메소드에 활용될 수 있습니다.
  • 로그 메시지 출력: Console.WriteLine 메소드처럼 다양한 형식의 인수를 받아 콘솔에 출력하는 메소드를 구현할 때 유용합니다.

가변 개수의 인수를 활용하실 때 아래 사항을 주의하세요.

  • params 키워드를 사용한 매개변수는 배열 형식이어야 합니다.
  • params 키워드를 사용한 매개변수는 메소드 선언 시 하나만 지정할 수 있으며, 매개변수 목록의 가장 마지막에 위치해야 합니다.
  • 가변 개수의 인수를 너무 많이 사용하는 것은 메소드 호출 시 혼란을 야기할 수 있습니다. 적절한 상황에서 사용하는 것이 중요합니다.

명명된 인수

명명된 인수(Named Arguments)는 메소드를 호출할 때 인수의 순서를 따르지 않고, 매개변수 이름을 명시적으로 지정하여 값을 전달하는 기능입니다. 명명된 인수를 사용하면 코드 가독성을 높이고, 특히 선택적 매개변수가 많은 메소드를 사용할 때 유용합니다.

명명된 인수 사용 방식:

  1. 메소드 호출 시: 매개변수_이름: 값 형태를 사용하여 인수를 전달합니다. 매개변수 이름은 메소드 선언 시 정의된 이름을 사용합니다.
public static void PrintOrder(string productName, string customerName, string address)
{
    Console.WriteLine($"주문 제품: {productName}");
    Console.WriteLine($"고객 이름: {customerName}");
    Console.WriteLine($"배송 주소: {address}");
}

public static void Main(string[] args)
{
    // 순서대로 인수 전달(일반적인 방식)
    PrintOrder("노트북", "김철수", "서울시 강남구");

    // 명명된 인수를 사용하여 인수 전달(순서 변경 가능)
    PrintOrder(customerName: "박영희", productName: "마우스", address: "경기도 성남시");
}

명명된 인수의 특징은 아래와 같습니다.

  • 인수 순서 무관: 메소드 선언 시 매개변수 순서와 상관없이, 원하는 순서로 인수를 전달할 수 있습니다. 위 예시에서 두 번째 PrintOrder 호출은 매개변수 순서를 변경하여 전달했습니다.
  • 코드 가독성 향상: 각 인수가 어떤 매개변수에 해당하는지 명확하게 알 수 있으므로, 코드의 의미를 쉽게 파악할 수 있습니다. 특히 매개변수 이름이 의미 있는 경우 가독성 향상 효과가 큽니다.
  • 선택적 매개변수와 함께 사용 시 유용: 선택적 매개변수가 많은 메소드를 사용할 때, 특정 선택적 매개변수만 값을 지정하고 나머지는 기본값을 사용하고 싶을 때 명명된 인수를 사용하면 코드를 간결하게 유지할 수 있습니다.(6.10 선택적 인수 챕터에서 자세히 다룹니다.)

명명된 인수의 활용 시나리오

  • 매개변수가 많은 메소드: 매개변수 개수가 많고, 각 매개변수의 의미를 명확히 알고 싶을 때 명명된 인수를 사용하면 코드 가독성을 높일 수 있습니다.
  • 선택적 매개변수 활용: 선택적 매개변수 중 일부만 값을 지정하고 싶을 때, 명명된 인수를 사용하면 원하는 매개변수만 선택적으로 값을 할당할 수 있습니다.

주의사항은 아래와 같습니다.

  • 명명된 인수와 위치 기반 인수(순서대로 전달하는 인수)를 혼합하여 사용할 수 있지만, 위치 기반 인수가 먼저 와야 하고, 그 다음에 명명된 인수가 와야 합니다. 명명된 인수 뒤에 위치 기반 인수를 사용하는 것은 불가능합니다.
  • 명명된 인수를 사용할 때는 반드시 매개변수 이름을 정확하게 입력해야 합니다. 오타가 있을 경우 컴파일 오류가 발생합니다.

선택적 인수

선택적 인수(Optional Arguments)는 메소드 선언 시 매개변수에 기본값을 지정하여, 메소드 호출 시 해당 인수를 생략할 수 있도록 하는 기능입니다. 선택적 인수를 사용하면 메소드 호출 방식을 유연하게 만들고, 메소드 오버로딩을 줄여 코드 복잡성을 감소시킬 수 있습니다.

  • 메소드 선언 시: 매개변수 목록에서 선택적 인수로 사용할 매개변수에 기본값을 할당합니다. 기본값은 상수, 기본값 형식의 리터럴, 또는 default(형식) 표현식을 사용할 수 있습니다.
  • 메소드 호출 시: 선택적 인수에 값을 전달하거나, 생략할 수 있습니다. 인수를 생략하면 해당 매개변수는 선언 시 지정한 기본값을 사용합니다.

선택적 인수의 예시:

public static void SendEmail(string to, string subject, string body, string from = "default@example.com", bool isHtml = false)
{
    Console.WriteLine($"To: {to}");
    Console.WriteLine($"Subject: {subject}");
    Console.WriteLine($"Body: {body}");
    Console.WriteLine($"From: {from}");
    Console.WriteLine($"Is HTML: {isHtml}");
}

public static void Main(string[] args)
{
    // 필수 인수만 전달(선택적 인수는 기본값 사용)
    SendEmail("user1@example.com", "안녕하세요", "메일 내용입니다.");
    Console.WriteLine("---");
    // 선택적 인수 값 지정(from, isHtml)
    SendEmail("user2@example.com", "제목", "내용", from: "sender@example.com", isHtml: true);
}

선택적 인수의 특징은 아래와 같습니다.

  • 인수 생략 가능: 메소드를 호출할 때 선택적 인수를 생략하면, 해당 매개변수는 메소드 선언 시 지정한 기본값을 사용합니다. 위 예시의 첫 번째 SendEmail 호출에서는 fromisHtml 인수를 생략하여 기본값이 사용되었습니다.
  • 오버로딩 감소: 선택적 인수를 사용하면 동일한 기능을 수행하지만, 인수 조합이 다른 여러 개의 메소드를 오버로딩하는 대신, 하나의 메소드로 다양한 호출 방식을 지원할 수 있습니다. 코드 중복을 줄이고 유지 보수를 용이하게 합니다.
  • 가독성 향상(명명된 인수와 함께 사용 시): 선택적 인수를 명명된 인수와 함께 사용하면, 코드 가독성을 더욱 높일 수 있습니다. 필요한 선택적 인수에만 값을 명시적으로 지정하고, 나머지는 기본값을 사용하도록 할 수 있습니다.

선택적 인수의 활용 시나리오

  • 기본 동작이 있는 메소드: 대부분의 경우 기본값으로 동작하지만, 특정 상황에서만 다른 값을 지정해야 하는 매개변수에 선택적 인수를 사용하면 편리합니다. 위 예시의 SendEmail 메소드의 from 주소나 HTML 형식 여부 등이 이에 해당합니다.
  • API 설계: 라이브러리 또는 API를 설계할 때, 사용자에게 다양한 옵션을 제공하면서도 기본적인 사용법을 간결하게 유지하고 싶을 때 선택적 인수를 활용하면 좋습니다.

주의사항은 아래와 같습니다.

  • 선택적 매개변수는 매개변수 목록의 가장 뒤쪽에 위치해야 합니다. 필수 매개변수 뒤에 와야 합니다.
  • 선택적 매개변수의 기본값은 컴파일 타임에 결정되는 상수 값이어야 합니다. 메소드 호출 시 계산되는 값이나, 다른 변수의 값을 기본값으로 사용할 수 없습니다.
  • 과도한 선택적 인수 사용은 오히려 코드의 가독성을 저해할 수 있습니다. 적절한 개수의 선택적 인수를 사용하는 것이 중요합니다.

로컬 함수

로컬 함수(Local Function)는 메소드 내부에 정의된 메소드입니다. 로컬 함수는 자신을 감싸고 있는 외부 메소드(Enclosing Method) 내에서만 호출될 수 있으며, 외부 메소드의 지역 변수에 접근할 수 있습니다. 로컬 함수는 C# 7.0 버전부터 도입된 기능으로, 특정 메소드 내에서만 사용되는 작은 기능들을 캡슐화하고 코드 가독성을 높이는 데 유용합니다.

  • 메소드 내부: 메소드 본문 내부에 로컬 함수를 정의합니다. 로컬 함수는 메소드와 동일한 형태(한정자, 반환 형식, 이름, 매개변수 목록, 본문)로 구성됩니다.
  • 한정자 제약: 로컬 함수는 접근 한정자(public, private 등)를 명시적으로 지정할 수 없습니다. 항상 private 접근 수준을 가집니다. 즉, 로컬 함수를 정의한 외부 메소드 내에서만 호출 가능합니다.
  • static 한정자: C# 8.0부터는 로컬 함수에 static 한정자를 추가할 수 있습니다. static 로컬 함수는 외부 메소드의 지역 변수에 접근할 수 없지만, 성능상의 이점을 얻을 수 있습니다.
public static int CalculateFactorial(int n)
{
    if (n < 0)
    {
        throw new ArgumentException("팩토리얼은 0 이상의 정수만 계산할 수 있습니다.");
    }

    // 로컬 함수 정의: 재귀적으로 팩토리얼 계산
    int FactorialRecursive(int num)
    {
        if (num == 0)
        {
            return 1;
        }
        return num * FactorialRecursive(num - 1); // 재귀 호출
    }

    return FactorialRecursive(n); // 로컬 함수 호출
}

public static void Main(string[] args)
{
    int result = CalculateFactorial(5);
    Console.WriteLine($"5! = {result}"); // 5! = 120
}

로컬 함수의 특징은 아래와 같습니다.

  • 캡슐화: 특정 메소드 내에서만 사용되는 기능을 로컬 함수로 캡슐화하여 코드의 모듈성을 높이고, 이름 충돌 가능성을 줄일 수 있습니다. 위 예시에서 FactorialRecursive 로컬 함수는 CalculateFactorial 메소드 내에서만 사용되는 팩토리얼 계산 로직을 담당합니다.
  • 가독성 향상: 복잡한 메소드 내부에서 작은 기능들을 로컬 함수로 분리하면 코드 가독성을 높일 수 있습니다. 특히 재귀 호출과 같이 특정 알고리즘을 구현할 때 로컬 함수를 활용하면 코드를 명확하게 구조화할 수 있습니다.
  • 클로저(Closure): 로컬 함수는 자신을 감싸는 외부 메소드의 지역 변수에 접근할 수 있습니다. 이를 클로저라고 하며, 로컬 함수가 외부 메소드의 상태를 공유하고 변경할 수 있도록 합니다.

로컬 함수의 활용 시나리오

  • 재귀 알고리즘 구현: 재귀 호출을 사용하는 알고리즘을 구현할 때, 재귀 로직을 로컬 함수로 분리하면 코드를 더 명확하고 이해하기 쉽게 만들 수 있습니다. 위 예시의 팩토리얼 계산 로직이 재귀 로직을 로컬 함수로 구현한 경우입니다.
  • 반복적인 작업 캡슐화: 메소드 내에서 반복적으로 수행되는 작은 작업들을 로컬 함수로 캡슐화하여 코드 중복을 줄이고, 가독성을 높일 수 있습니다.
  • 람다 식 대체(복잡한 로직): 람다 식으로 표현하기 어려운 복잡한 로직을 로컬 함수로 구현하여 사용할 수 있습니다.

주의사항은 아래와 같습니다.

  • 로컬 함수는 외부 메소드 내에서만 호출할 수 있습니다. 외부 메소드 밖에서 로컬 함수를 호출하려고 하면 컴파일 오류가 발생합니다.
  • 과도한 로컬 함수 사용은 오히려 코드의 복잡성을 증가시킬 수 있습니다. 로컬 함수는 작고 명확한 기능 단위로 분리하는 데 사용하는 것이 적절합니다.
  • static 로컬 함수는 외부 메소드의 지역 변수에 접근할 수 없다는 제약 사항이 있습니다. 필요한 경우 static 키워드를 생략하여 클로저 기능을 활용하거나, 필요한 데이터를 매개변수로 전달해야 합니다.