메서드는 입력(매개변수)을 받아 일을 하고 결과(반환값)를 돌려줄 수 있는, 이름 붙은 코드 블록입니다. C#에서 모든 메서드는 타입 안에 있으며, 독립된 함수는 없습니다.
출력:
Headphones 48.00
Cable 11.40
메서드의 구조
public static decimal PriceWithTax(decimal net, decimal taxRate)
{
return net * (1 + taxRate);
}
- 접근 한정자(
public,private, ...): 누가 호출할 수 있는지 정합니다. 생략하면 메서드는 자기 타입에private입니다. static: 메서드가 객체가 아니라 타입에 속합니다. 아래에서 자세히 다룹니다.- 반환 타입: 반환하는 값의 타입, 반환값이 없으면
void입니다. - 이름: 관례상 PascalCase이며 보통 동사입니다(
Calculate,SendEmail,TryParse). - 매개변수 목록: 0개 이상의
type name쌍입니다. 각 매개변수는 호출하는 쪽의 인수로 초기화되는 지역 변수입니다. - 본문: 중괄호 사이의 문장들입니다.
이름과 매개변수 타입(그리고 ref, out, in 한정자)이 메서드의 시그니처를 이룹니다. 반환 타입은 시그니처에 포함되지 않으며, 이는 오버로딩에서 중요합니다.
반환값과 void
return은 메서드를 즉시 끝내고 값을 돌려줍니다. 반환 타입이 있는 메서드는 모든 경로에서 그 타입의 값을 반환해야 합니다:
static string Grade(int score)
{
if (score >= 50)
return "pass";
} // error CS0161: 'Program.Grade(int)': not all code paths return a value
컴파일러는 모든 분기를 검사합니다. 빠진 return "fail";을 추가하거나, 그 지점에 도달하는 것이 버그라면 throw를 추가하세요.
void 메서드는 아무것도 반환하지 않습니다. 그래도 값 없는 return;으로 일찍 빠져나갈 수 있습니다:
출력:
ana@mail.com: newsletter sent
ben@mail.com: not subscribed, skipped
False
True
식 본문 메서드
메서드 본문이 식 하나라면 =>가 중괄호와 return을 대신합니다:
static decimal Discount(decimal price) => price * 0.1m;
static bool IsAdult(int age) => age >= 18;
static void Log(string message) => Console.WriteLine($"[log] {message}");
같은 메서드를 더 짧게 쓴 것입니다. 본문이 식 하나(호출, 대입)라면 void 메서드에도 쓸 수 있습니다.
매개변수는 복사본입니다
인수는 기본적으로 값으로 전달됩니다. 매개변수는 인수의 복사본을 받습니다. 메서드 안에서 매개변수에 대입해도 호출하는 쪽의 변수에는 영향이 없습니다:
출력:
inside: 42
after: 21
scores: 70, 85, 100
List가 바뀐 이유는 참조 형식에서 복사되는 값이 참조이기 때문입니다. 메서드와 호출하는 쪽이 같은 리스트 객체를 가리킵니다. 메서드 안에서 scores = new List<int>()로 다시 대입해도 호출하는 쪽에는 영향이 없습니다. 메서드가 호출하는 쪽의 변수에 대입하게 하려면 ref나 out을 쓰고, 매개변수에 기본값을 주고 이름으로 호출하려면 선택적 매개변수와 명명된 인수를 쓰세요.
튜플로 여러 값 반환하기
C# 7.0부터 메서드는 이름 붙은 요소를 가진 튜플을 반환할 수 있고, 호출하는 쪽은 이를 별도의 변수로 분해할 수 있습니다:
출력:
min 15, max 27, avg 21.0
range 12
_는 필요 없는 요소를 버립니다. 여러 곳에서 쓰는 결과라면 이름 붙은 속성을 가진 작은 클래스나 레코드가 튜플보다 모양을 더 잘 문서화합니다.
오버로딩
매개변수 목록의 개수, 타입, 타입 순서가 다르면 여러 메서드가 같은 이름을 공유할 수 있습니다. 컴파일러는 호출마다 가장 잘 맞는 것을 고릅니다:
출력:
12.57
13.50
25
Area(5)는 정확히 일치하는 int 버전을, Area(2.0)은 double 버전을 고릅니다. 메서드는 반환 타입만 다를 수는 없습니다. int와 double을 반환하는 두 Area(int) 메서드는 오류 CS0111 Type 'Program' already defines a member called 'Area' with the same parameter types입니다. Area(5) 같은 호출로는 둘을 구분할 수 없기 때문입니다.
어떤 오버로드도 받지 않는 호출도 컴파일 시점에 실패합니다. Area(1, 2, 3)은 CS1501 No overload for method 'Area' takes 3 arguments, Area("big")은 CS1503 Argument 1: cannot convert from 'string' to 'double'입니다.
params: 개수가 정해지지 않은 인수
params 배열 매개변수는 인수를 몇 개든(0개 포함) 받습니다. 컴파일러가 배열을 만들어 줍니다:
출력:
empty cart: 0 item(s), 0
one item: 1 item(s), 9.99
three items: 3 item(s), 19.75
from array: 2 item(s), 50
규칙: params는 마지막 매개변수여야 하고(그렇지 않으면 CS0231), 메서드마다 최대 하나이며, 기존 배열을 넘기면 그 배열이 그대로 전달됩니다. Console.WriteLine("{0} and {1}", a, b)와 string.Format은 params object[] 메서드입니다. C# 13은 params를 List<T>와 ReadOnlySpan<T> 같은 다른 컬렉션 타입으로 확장했습니다. 그 전에는 배열만 허용되었습니다.
정적 메서드와 인스턴스 메서드
static 메서드는 타입에 속하며 타입 이름으로 호출합니다: Math.Max(3, 7), Program.Total(...). 인스턴스 메서드는 객체에 속하며 그 객체의 필드를 쓸 수 있습니다:
출력:
12
객체 없이 정적 메서드에서 인스턴스 메서드를 호출하는 것은 C#에서 가장 흔한 입문자 오류입니다:
class Program
{
void Greet() => Console.WriteLine("hi");
static void Main()
{
Greet();
// error CS0120: An object reference is required for the non-static field, method, or property 'Program.Greet()'
}
}
Greet를 static으로 표시하거나, 객체를 만들어 그 객체에서 호출하세요: new Program().Greet();. 정적 멤버와 정적 클래스는 static 페이지에서 더 자세히 다룹니다.
Main 메서드
Main은 프로그램이 시작하는 곳입니다. 반드시 static이어야 하며, 다음 시그니처가 올바릅니다:
static void Main() { }
static void Main(string[] args) { } // command-line arguments
static int Main(string[] args) { return 0; } // exit code for the shell
static async Task Main() { } // C# 7.1 and later
async Task Main 형태는 진입점에서 바로 await할 수 있게 해 줍니다(async와 await 참고). C# 9부터는 파일에 대신 최상위 문을 둘 수 있습니다. 새 dotnet new console 프로젝트처럼 클래스도 Main도 전혀 없는 코드입니다. 컴파일러가 그 주위에 클래스와 Main 메서드를 생성하므로 같은 것을 짧게 쓴 것이며, args도 여전히 쓸 수 있습니다.
재귀
메서드는 자기 자신을 호출할 수 있습니다. 호출마다 자기만의 매개변수와 지역 변수를 가지며, 기저 사례가 호출 사슬을 멈춰야 합니다:
출력:
120
2432902008176640000
35
기저 사례가 없거나 입력이 너무 깊으면 호출마다 스택 프레임이 쌓여 결국 StackOverflowException으로 프로그램이 죽습니다. 이 예외는 잡을 수 없으므로, 깊이를 알 수 없는 입력에는 재귀를 루프로 다시 쓰세요.
로컬 함수
C# 7.0은 로컬 함수를 도입했습니다. 다른 메서드 안에 선언되어 그 안에서만 보이는 메서드입니다. 둘러싼 메서드의 변수를 쓸 수 있습니다:
// C# 7.0 and later
static int CountValid(string[] codes)
{
int count = 0;
foreach (string code in codes)
{
if (IsValid(code)) count++;
}
return count;
bool IsValid(string c) => c.Length == 6 && char.IsLetter(c[0]);
}
헬퍼를 클래스의 나머지에 노출하지 않고 유일한 호출자 옆에 둘 수 있습니다. C# 8부터는 static으로 표시해 바깥 변수의 캡처를 금지할 수 있습니다. 로컬 함수를 쓸 수 없는 곳에서는 이 페이지의 다른 예제처럼 같은 클래스의 private static 메서드가 같은 일을 합니다.
자주 묻는 질문
C#에서 메서드와 함수의 차이는 무엇인가요?
C#에서는 모든 함수가 클래스, 구조체, 레코드 안에 선언되며 메서드라고 부릅니다. 독립된 함수는 없습니다. 최상위 문(C# 9)과 로컬 함수(C# 7)도 생성된 타입이나 둘러싼 타입의 메서드로 컴파일됩니다. "함수"라는 말은 비공식적으로, 그리고 람다와 델리게이트에 대해 쓰입니다.
C#에서 params는 무엇을 하나요?
params는 메서드가 한 타입의 인수를 개수 제한 없이 받게 합니다. static int Sum(params int[] numbers)는 Sum(), Sum(4), Sum(4, 5, 6), Sum(existingArray)로 호출할 수 있습니다. 컴파일러가 인수들을 배열로 묶습니다. params 매개변수는 마지막이어야 하며, 메서드마다 하나만 둘 수 있습니다.
C#의 Main 메서드란 무엇인가요?
Main은 프로그램의 진입점, 즉 런타임이 가장 먼저 호출하는 메서드입니다. 반드시 static이어야 하고, void나 int(종료 코드)를 반환할 수 있으며, 커맨드라인 인수를 위한 string[] args를 받을 수 있습니다. C# 7.1부터는 async Task Main도 가능하고, C# 9부터는 최상위 문을 쓰면 컴파일러가 대신 생성해 줍니다.
C# 메서드에서 여러 값을 반환하려면 어떻게 하나요?
튜플을 반환하세요: static (int Min, int Max) Range(int[] values)와 return (min, max);. 호출하는 쪽은 result.Min을 읽거나 var (lo, hi) = Range(values);로 분해할 수 있습니다. 코드 전반에서 함께 다니는 값이라면 작은 클래스나 레코드가 더 명확하고, out 매개변수는 예전의 대안입니다.
"An object reference is required for the non-static field, method, or property" 오류가 나는 이유는 무엇인가요?
Main 같은 static 메서드가 인스턴스 메서드를 직접 호출했기 때문입니다. 정적 메서드는 타입에 속하므로 호출할 대상 객체가 없습니다. 호출되는 메서드를 static으로 표시하거나, 먼저 객체를 만드세요: var app = new Program(); app.Greet();.
"not all code paths return a value"는 무슨 뜻인가요?
CS0161 오류입니다. 반환 타입이 있는 메서드에 return 없이 닫는 중괄호에 도달하는 경로가 최소 하나 있다는 뜻입니다. 보통 if는 값을 반환하는데 그 뒤에 else나 마지막 return이 없는 경우입니다. 메서드 끝에 return(또는 throw)을 추가하세요.