'이것이 C#이다 개정판'의 chapter 6을 정리한 글입니다.
메소드 (Method)
메소드는 일련의 코드를 하나의 이름 아래 묶은 것을 일컬어 말합니다.
class 클래스_이름
{
한정자 반환_형식 메소드_이름( 매개변수_목록)
{
// 실행하고자 하는 코드
return 메소드_결과;
}
}
- 클래스
- 클래스 정의를 시작하는 키워드입니다.
- 클래스는 객체 지향 프로그래밍의 기본 단위로, 객체를 생성하는 템플릿 역할을 합니다.
- 한정자
- 메소드의 속성을 수식하는 한정자를 둘 수 있습니다.
- 메소드의 접근 수준을 정의합니다.
- public : 모든 클래스에서 접근 가능
- private : 같은 크랠스 내에서만 접근 가능
- protected : 상속받은 클래스에서 접근 가능
- internal : 같은 어셈블리 내에서 접근 가능
- 반환 형식
- 메소드가 반환하는 값의 데이터 유형을 정의합니다.
- 메개변수 목록
- 메소드가 입력으로 받을 수 있는 변수의 목록입니다.
- 매개변수는 여러 개일 수 있으며, 쉼표로 구분합니다.
- return 메소드 결과
- 메소드의 반환형식이 void가 아닌 경우, 메소드의 결과를 반환하는 구문입니다.
- 반환형식이 void일 경우에도 아무 결과도 반환하지 않게끔 return 문을 사용하는 것은 가능합니다.
- 점프문의 한 종류인 return 키워드를 사용하여 결과를 반환합니다.
- 반환할 값의 데이터 형은 메소드의 반환 형식과 일치해야 합니다.
- 메소드의 반환형식이 void가 아닌 경우, 메소드의 결과를 반환하는 구문입니다.
매개변수
[ 매개변수(Parameter)와 인수(Argument) ]
class Calculator
{
public static int Plus (int a, int b)
{
// 입력받은 값을 더하여 반환하는 코드
}
}
class MainApp
{
public static void Main()
{
int x = 3;
int y = 4;
int result = Calculator.Plus(x, y);
// ...
}
}
위의 코드에서 x, y를 Calculator.Plus( ) 메소드를 호출할 때의 값을 전달하는 역할을 합니다. 그리고 그 값은 인수라고 불립니다.
여기서는 x와 y가 인수입니다.
x, y는 각각 Plus( ) 메소드의 매개변수 a, b로 값(인수)을 데이터로써 복사하여 전달합니다.
정리하자면,
매개변수는 메소드가 호출자에게서 전달받은 값을 받는 변수를 말하고, 인수는 호출자가 매개변수에 넘기는 값을 뜻합니다.
[ 참조에 의한 매개변수 전달 ]
위의 Plus( ) 메소드에서는 매개변수 a와 b는 x와 y의 영향을 주지 못합니다.
[예시]
public static void Swap(int a, int b)
{
int temp = b;
b = a;
a = temp;
}
위의 swap() 메소드처럼 원하는 변수의 값을 서로 바꾸는 코드가 존재할 때,
매개변수 a와 b는 값의 변수만 전달받기 때문에, 실제 변수의 값을 바꾸지는 못합니다.
그러기 위해서 필요한 것이 '참조에 의한 매개변수 전달'입니다.
C#에서 참조에 의한 매개변수 전달은 ref 키워드를 사용하면 됩니다.
[예시]
class Calculator
{
public static void Swap(ref int a, ref int b)
{
int temp = b;
b = a;
a = temp;
}
}
class MainApp
{
public static void Main()
{
int x = 3;
int y = 4;
Calculator.Swap(ref x, ref y);
// ...
}
}
이런 식으로 매개변수 앞에 ref 키워드를 붙여주고, 메소드를 호출할 때 역시 ref 키워드를 전달하는 인수 앞에 붙여주면 됩니다.
Swap(ref x, ref y)를 통해 Swap( )메소드에는 x와 y의 메모리 주소가 매개변수 a와 b에 각각 전달이 됩니다.
a와 b가 x와 y의 메모리 주소를 참조하고 있기 때문에, a의 값이나 b의 값을 변경하면 실제로 x와 y의 값도 변경되게 됩니다.
[ C#의 포인터 ]
ref 키워드를 사용하여 참조로 매개변수를 전달한 것처럼, 포인터를 사용해서도 같은 결과를 얻을 수 있습니다.
하지만 C#에서는 기몬적으로 포인터 사용이 제한되어 있습니다.
C#은 안전성을 중시하는 언어로 설계되었기 때문에, 메모리 관리와 관련하여 많은 제약을 두고 있습니다.
unsafe 키워드를 사용하면, 포인터를 사용할 수 있기는 합니다. unsafe 키워드는 코드가 안전하지 않음을 명시하는 것이죠.
C#에서 포인터 사용이 권장되지는 않지만, 재미삼아 한번 진행해보았습니다.
[예시]
using System;
using static System.Console;
namespace SwapByPointer
{
public static unsafe void PointerSwap(int* a, int* b)
{
int temp = *b;
*b = *a;
*a = temp;
}
}
class MainApp
{
static void Main(string[] args)
{
int x = 3;
int y = 4;
unsafe
{
Swap.PointerSwap(&x, &y);
}
WriteLine($"x : {x}, y : {y}");
}
}
}
참조 반환값
참조 반환값을 이용하면 메소드의 호출자로 하여금 반환받은 결과를 참조로 다룰 수 있도록 합니다.
ref 한정자를 이용해서 메소드를 선언하고, return 문이 반환하는 변수 앞에도 ref 키워드를 명시해야 합니다.
Class SomeClass
{
int SomeValue = 10;
public ref int SomeMethod() // ref 키워드로 메소드를 한정합니다.
{
// ...
return ref SomeValue; // return 문을 사용할 때 ref 키워드를 반환할 필드나 객체 앞에 붙여줍니다.
}
}
SomeClass obj = new SomeClass();
int result = obj.SomeMethod(); // 값으로 반환받고자 할 때는 그냥 메소드를 호출하면 됩니다.
ref int result = res obj.SomeMethod(); // result는 참조 지역 변수입니다.
[예시]
using System;
using static System.Console;
namespace RefReturn
{
class Product
{
private int price = 100;
public ref int GetPrice()
{
return ref price;
}
public void PrintPrice()
{
WriteLine($"Price : {price}");
}
}
class MainApp
{
static void Main(string[] args)
{
Product carrot = new Product();
ref int ref_local_price = ref carrot.GetPrice();
// ref_local_price를 수정하면 carrot.price의 내용도 바뀝니다.
int normal_local_price = carrot.GetPrice();
carrot.PrintPrice();
WriteLine($"Ref Local Price : {ref_local_price}");
WriteLine($"Normal Local Price : {normal_local_price}");
ref_local_price = 200;
carrot.PrintPrice();
WriteLine($"Ref Local Price : {ref_local_price}");
WriteLine($"Normal Local Price : {normal_local_price}");
}
}
}

[사용 용도]
1. 큰 데이터 구조
- 큰 구조체나 배열과 같은 데이터 구조를 반환할 때, 복사하는 대신 참조를 반환하면 성능을 크게 개선할 수 있습니다.
- 예를 들어, 대용량의 데이터를 포함하는 객체를 반환할 때 참조를 사용하면 불필요한 복사를 피할 수 있습니다.
2. 상태 변경
- 객체의 상태를 변경하고자 할 때, 해당 객체에 대한 참조를 반환하면 호출하는 쪽에서 그 객체의 속성을 직접 수정할 수 있습니다. 이는 메소드에서 객체를 반환하고 그 객체의 속성을 변경할 수 있는 경우에 유용합니다.
3. 구조체의 경우
- C#에서 구조체는 값 타입이므로, 메소드에서 구조체를 반환할 경우 복사가 이루어집니다.
이럴 때 참조를 반환하여 복사를 방지할 수 있습니다.
class Person
{
public string Name;
}
class Program
{
private static Person person = new Person { Name = "Bob" };
public static ref Person GetPerson()
{
return ref person; // Person 객체의 참조를 반환합니다.
}
public static void Main()
{
ref Person p = ref GetPerson(); // 참조를 가져옵니다.
p.Name = "Alice"; // Person의 Name을 변경합니다.
WriteLine(person.Name); // 출력 : Alice
}
}
출력 전용 매개변수 out
out 키워드를 사용하지 않고, 메소드의 두 개 이상의 결과를 반환할 수는 있습니다.
void Divide( int a, int b, ref int quotient, ref int remainder )
{
quotient = a / b;
remainder = a % b;
}
int a = 20, b = 3, c = 0, d = 0;
Divide( a, b, ref c, ref d );
Console.WriteLine("Quotient : {0}, Remainder {1}", c, d );
이런 식으로 ref 키워드를 활용하면 됩니다.
하지만, C#에서는 조금 더 안전한 방법으로 똑같은 일을 할 수 있게 해줍니다.
바로 out 키워드를 이용한 '출력 전용 매개변수'를 사용하는 것입니다.
void Divide( int a, int b, out int quotient, out int remainder )
{
quotient = a / b;
remainder = a % b;
}
int a = 20, b = 3, c, d;
Divide( a, b, out c, out d );
Console.WriteLine("Quotient : {0}, Remainder {1}", c, d );
위의 코드는 사실 ref 키워드에서 out 키워드로만 변경한 코드입니다.
겉으로 보기에는 거의 똑같지만, out에는 ref에 없는 안전장치가 있습니다.
[ ref 키워드와 out 키워드의 차이 ]
- 초기화 요구
- ref 키워드를 사용하는 경우, 호출하는 쪽에서 해당 변수가 반드시 초기화되어 있어야 합니다.
즉, ref 매개변수는 메소드 호출 전에 값이 할당되어 있어야 합니다. - out 키워드를 사용하는 경우, out 매개변수는 메소드 호출 전에 초기화할 필요가 없습니다.
즉, 호출하는 쪽에서 값을 할당하지 않아도 됩니다. 컴파일러가 호출당하는 메소드에서 그 지역 변수를 할당할 것을 보장하기 때문입니다.
- ref 키워드를 사용하는 경우, 호출하는 쪽에서 해당 변수가 반드시 초기화되어 있어야 합니다.
- 컴파일러 에러
- ref 키워드를 사용하는 경우, 메소드가 ref 매개변수의 값을 변경하지 않더라도 컴파일러는 경고를 발생시키지 않습니다.
즉, 메소드는 매개변수의 값을 읽기만 할 수 있고 값 변경이 필수가 아닙니다. - out 키워드를 사용하는 경우, 메소드 내에서 반드시 해당 매개변수에 값을 할당해야 합니다.
만약 메소드가 out 매개변수에 저장하지 않으면, 컴파일러는 할당되지 않은 변수를 사용했다는 컴파일 에러를 발생시킵니다.
- ref 키워드를 사용하는 경우, 메소드가 ref 매개변수의 값을 변경하지 않더라도 컴파일러는 경고를 발생시키지 않습니다.
[예시]
class Program
{
public static void RefExample(ref int a)
{
// a의 값을 변경하지 않아도 경고가 없습니다.
a += 10; // a의 값을 변경
}
public static void OutExample(out int b)
{
// b에 값을 반드시 할당해야 합니다.
b = 20; // b에 값을 할당
}
public static void Main()
{
int x = 5;
RefExample(ref x); // x는 5로 초기화되어야 합니다.
Console.WriteLine(x); // 출력: 15
int y; // 초기화 필요 없음
OutExample(out y); // y는 값이 할당되어야 합니다.
Console.WriteLine(y); // 출력: 20
}
}
메소드 오버로딩 (Method Overloading)
메소드 오버로딩은 같은 이름의 메소드를 여러 개 정의하되, 매개변수의 수나 타입이 다르게 설정하는 것을 의미합니다.
[특징]
- 메소드 이름이 같음 : 오버로딩된 메소드는 동일한 이름을 가집니다.
- 매개변수의 차이 : 매개변수의 수 또는 타입이 달라야 합니다.
- 반환 타입은 구분되지 않음 : 반환 타입만으로는 오버로딩이 불가능합니다. 반드시 매개변수의 정의가 달라야 합니다.
using System;
class Calculator
{
// 두 정수의 합을 계산하는 메소드
public int Add(int a, int b)
{
return a + b;
}
// 세 정수의 합을 계산하는 메소드
public int Add(int a, int b, int c)
{
return a + b + c;
}
// 두 실수의 합을 계산하는 메소드
public double Add(double a, double b)
{
return a + b;
}
}
class Program
{
public static void Main()
{
Calculator calc = new Calculator();
int sum1 = calc.Add(3, 4); // 두 정수의 합
int sum2 = calc.Add(3, 4, 5); // 세 정수의 합
double sum3 = calc.Add(2.5, 3.7); // 두 실수의 합
Console.WriteLine($"Sum of 3 and 4: {sum1}"); // 출력: Sum of 3 and 4: 7
Console.WriteLine($"Sum of 3, 4 and 5: {sum2}"); // 출력: Sum of 3, 4 and 5: 12
Console.WriteLine($"Sum of 2.5 and 3.7: {sum3}"); // 출력: Sum of 2.5 and 3.7: 6.2
}
}
가변 개수의 인수 (Variable Argument List)
프로그래밍을 하면서 메소드 오버로딩을 사용하지 않고, 인수의 개수만 다르게 여러 버전으로 오버 로딩을 하고 싶을 때가 있습니다.
이런 경우를 위해 C#은 "가변 개수의 인수"라는 기능을 제공합니다. 이 기능을 위해서는 params 키워드를 사용해야 합니다.
int Sum( params int[] args)
{
int sum = 0;
for(int i = 0; i < args.Length; i++)
{
sum += args[i];
}
return sum;
}
명명된 인수 (Named Argument)
메소드를 호출할 때 매개변수 목록 중 어느 매개변수에 데이터를 할당할지 지정하는 기준은 순서입니다.
C#에서는 또 다른 스타일도 지원을 하는데, '명명된 인수'라는 것입니다.
명명된 인수를 사용하기 위해서는 메소드를 호출할 때, 인수의 이름 뒤에 콜론( : )을 붙인 뒤 그 뒤에 할당할 데이터를 넣어주면 됩니다.
static void PrintProfile(string name, string phone)
{
Console.WriteLine("Name: {0}, Phone : {1}", name, phone);
}
static void Main(string[] args)
{
PrintProfile(name : "홍길동", phone : "010-1234-1234");
}
[ 명명된 인수 사용의 장단점 ]
[장점]
- 가독성 향상
- 코드의 의도를 명확히 할 수 있습니다. 어떤 인수가 어떤 값을 의미하는지 쉽게 이해가 가능해집니다.
- 인수 순서 무시
- 인수의 순서를 무시하고 값을 전달할 수 있습니다. 이는 특히 매개변수의 수가 많거나 읽기 어려운 경우 유용합니다.
- 선택적 인수
- 기본값이 설정된 선택적 인수와 함께 사용할 수 있어, 필요한 인수만 명시하고 나머지는 기본값을 사용할 수 있습니다.
※ 이에 대한 개념은 밑에 더 자세하게 정리되어 있습니다.
- 기본값이 설정된 선택적 인수와 함께 사용할 수 있어, 필요한 인수만 명시하고 나머지는 기본값을 사용할 수 있습니다.
void SetValues(int x = 0, int y = 0) { }
// 선택적으로 x만 지정
SetValues(x: 10);
[단점]
- 코드의 복잡성 증가
- 명명된 인수를 사용할 때, 매개변수 이름이 바뀌면 메소드 호출 부분의 코드가 모두 영향을 받을 수 있습니다.
이는 유지보수 시 번거로울 수 있습니다.
- 명명된 인수를 사용할 때, 매개변수 이름이 바뀌면 메소드 호출 부분의 코드가 모두 영향을 받을 수 있습니다.
- 성능 오버헤드
- 컴파일러가 매개변수의 이름을 확인해야 하므로 약간의 성능 오버헤드가 발생할 수 있습니다.
하지만 이는 일반적인 상황에서는 미미한 영향을 미칩니다.
- 컴파일러가 매개변수의 이름을 확인해야 하므로 약간의 성능 오버헤드가 발생할 수 있습니다.
선택적 인수
선택적 인수는 메소드의 매개변수의 기본 값을 가지게 하는 것입니다. 기본 값을 가진 매개변수는 메소드를 호출할 때 해당 인수를 생략할 수 있습니다.
주의할 점은, 선택적 인수는 항상 필수 인수 뒤에 와야 합니다. 물론 필수 인수가 하나도 없는 경우에는 이 규칙에 신경 쓰지 않아도 됩니다.
void MyMethod_0( int a = 0 )
{
Console.WriteLine( "{0}", a );
}
void MyMethod_1(int a, int b =0 )
{
Console.WriteLine( "{0}, {1}", a, b );
}
void MyMethod_2( int a, int b, int c = 10, int d = 20 )
{
Console.WriteLine( "{0}, {1}, {2}, {3}", a, b, c, d );
}
// 호출 시
MyMethod_0(); // 출력 : 0
MyMethod_0(10); // 출력 : 10
MyMethod_1(10); // 출력 : 10, 0
MyMethod_1(10, 10); // 출력 : 10, 10
MyMethod_2(10, 10); // 출력 : 10, 10, 10, 20
MyMethod_2(0, 0, 0, 0); // 출력 : 0, 0, 0, 0
선택적 인수를 사용한 코드는, 내가 어느 매개변수에 어떤 인수를 할당했는지 분간이 잘 되지 않을 때도 있습니다.
이럴 때, 명명된 인수와 함께 사용하면 이런 문제를 줄일 수 있습니다.
메소드 오버로딩 vs 선택적 매개변수
선택적 인수는 상당히 유용한 기능임에 틀림없지만, 메소드 오버로딩과 함께 사용될 때 혼란을 야기할 수도 있습니다.
예를 들어 다음과 같이 두 개의 MyMethod( ) 메소드를 오버로딩한다고 합시다.
void MyMethod( string args0 = "", string args1 = "")
{
Console.WriteLine( "A" );
}
void MyMethod()
{
Console.WriteLine( "B" );
}
그리고 MyMethod()를 호출한다면, B가 출력됩니다.
정답은 중요치 않습니다. 위에서와 같이 선택적 인수와 오버로딩을 함께 사용한 것 자체가 잘못되었기 때문입니다.
오버로딩을 할지 아니면 선택적 인수를 사용할지를 프로그래머가 정책적으로 분명하게 정해야 합니다.
논리는 동일하되 매개변수가 다른 경우네는 선택적 인수를 사용하고, 매개변수에 따라 논리도 함께 달라지는 경우에는 오버로딩을 사용하는 식으로 말입니다.
로컬 함수 (Local Function)
로컬 함수는 메소드 안에서 선언되고, 선언된 메소드 안에서만 사용되는 특별한 함수입니다.
클래스의 멤버가 아니기 때문에 메소드가 아니라 함수(function)라고 불립니다.
calss SomeClass
{
public void SomeMethod() // 메소드 선언
{
int count = 0;
SomeLocalFunction(1, 2); // 로컬 함수 호출
SomeLocalFunction(3, 4);
void SomeLocalFunction(int a, int b) // 로컬 함수 선언
{
// ...
Console.WriteLine($"{count : {++count}");
// 로컬 함수는 자신이 속한 메소드의 지역 변수를 사용할 수 있습니다.
}
}
}'C#' 카테고리의 다른 글
| [C#] 객체지향 프로그래밍 : 은닉성, 상속성, 다형성 (0) | 2025.04.20 |
|---|---|
| [C#] 객체지향 프로그래밍 : 객체, 클래스 (0) | 2025.04.15 |
| [C#] Switch, 반복문, 점프문 (0) | 2025.04.10 |
| [C#] 연산자 (2) | 2025.04.07 |
| [C# 기본 정리 3] 형 변환, Nullable, var, 전역변수, 문자열 (0) | 2025.04.04 |