C# 메소드 기초 / Main 메소드 – C# 초급 10

C# 메소드 기초

이번 포스팅은 C# 메소드에 대해서 알아보겠습니다. C나 OOP가 아닌 다른 언어의 경험이 있다면 메소드는 함수(function)처럼 보입니다. 기능적으로 함수는 맞는데 메소드는 클래스의 멤버로써 함수와 차이가 있습니다.

다음 예제를 통해 알아보겠습니다. 일단 클래스의 메소드는 인스턴스가 만들어진 후에 사용가능합니다. 아래 SayHello 메소드는 MyClass에 속해있습니다. m1 에 인스턴스를 할당한 후에야 호출할 수 있습니다. C언어에서의 함수는 선언 한 다음 라인의 코드에서 호출하는 방식인 것을 감안하면 전혀 다른 구조입니다.

이렇게 하는 이유는 클래스 타입에 맞는 호출만 가능하도록 사용범위를 제한하기 위해서 입니다. 물론 private과 public 등 접근제한자로 개별 메소드의 외부 접근 기능을 결정할 수 있는데요. 메소드의 기본 접근제한자(default)는 private 입니다. private은 클래스 내부에서만 접근이 가능하고 public은 클래스 외부 어디서나 접근할 수 있습니다. OOP 에서는 접근제어자에 더해서 namespace 까지 체크를 해야 하는데요. 이것만 봐도 벌써 메소드와 함수에 큰 차이가 있음을 알 수 있습니다.

using System;

namespace MyApp
{
    public class Program
    {
        public static void Main(string[] args)
        {
            MyClass m1 = new MyClass();
            m1.SayHello();
        }
    }
    class MyClass
    {
        public void SayHello()
        {
            Console.WriteLine("Hello World!");
        }
    }
}

메소드는 { } 중괄호로 감싼 부분의 코드를 끝까지 실행시키거나 return 문에서 종료합니다.

이 메소드의 기본 원리는 함수에 기초하고 있어서 그 부분을 먼저 알면 좋은데 이래서 C언어를 먼저 배운 사람은 OOP 를 학습하기에도 좋습니다. C까지 돌아가는 것은 좀 그러니까… C++에서는 클래스 전에 함수부터 설명하는데 C#은 또 C++과는 다른 .NET 플랫폼의 OOP 언어라서 사실 그냥 전역 영역에 만들어서 사용하는 함수라는 개념은 없습니다. (C# 9 이후 최상위 문이 도입되서 비슷한 모양으로 코딩할 수는 있지만 MS 문서에서는 교육용이나 테스트용 정도로 권장한다)

암튼 다시 C#의 메소드로 돌아와서 메소드는 헤드(head)와 바디(body)로 구성됩니다. 헤드에는 메소드의 이름 SayHello 접근제어자 (public, private) 리턴타입 (int, double, void …) 그외 각종 지시자(static …) 가 들어가고 헤드의 ( ) 소괄호 안에는 매개변수(parameter)가 들어갑니다. ( ) 그냥 이렇게 쓴 것은 매개변수가 없는 경우입니다.

바디는 { } 중괄호 안의 코드들로 구성되며 C#의 문 statement 들이 들어갑니다. 아무 코드가 들어가는 것은 아니고 그 메소드의 목적에 맞는 코드가 들어갑니다. 어느 라인이건 return 문을 만나면 종료하는데 자신을 호출한 곳으로 복귀합니다. 예를 들어 Main 함수에서 메소드를 호출했다면 return 값을 가지고 다시 Main 함수로 돌아옵니다.

C#에서 프로그램이 시작하는 지점은 Main 함수(Main 메소드)입니다. 위에서 보면 Program 클래스의 Main 메소드입니다. 이것은 C#에서 시작점으로 약속한 것 입니다. 프로그램 실행시 소스코드의 어느 장소에서는 시작이 되야 하는데 그것을 Main 이라고 정한 것 입니다.

Main 메소드 설명

C# 을 시작하고 처음 Hello World 코드를 작성하기 시작하는 영역이 Main 메소드입니다. 어떤 언어로 작성한 프로그램에도 시작과 끝이 있어야 합니다. C#만 그런게 아니라 어떤 언어를 사용해도 같습니다. 프로그램이 시작되어 코드의 실행이 끝난 후에는 종료합니다. 소스코드가 단순하거나 복잡하거나 원리는 같습니다.

Main 은 그렇게 C#의 시작점으로 약속한 장소입니다. 물론 최종적으로 런타임에서 실행될 때까지의 과정은 훨씬 복잡합니다. .NET 의 실행과정을 아주 짧게 말하면 CLR이 메모리로 로드한 클래스에서 Main 메소드 부터 실행되고 모든 코드가 실행되면 메모리를 포함하여 사용한 모든 자원을 운영체제에 반환하고 종료합니다. (별로 짧지는 않지만;;)

Main 메소드는 public static void Main(string[] args) 가 헤더입니다. 지시자들은 각자 의미가 다 있는데 public -> 클래스 외부에서 사용할 수 있다고 해서 public 입니다. 위의 Program 클래스를 로드해서 사용하는 CLR의 입장에서 보면 Main 메소드는 private으로 접근합니다. 사실 Main을 public 으로 하는 것은 권장하지 않는데 CLR을 제외한 다른 클래스에서 접근 가능하기 때문입니다. 사실 이 부분은 같은 메소드 형태를 사용하는 자바와 유사하면서도 다른 것이 자바는 public을 기본으로 하고 C#은 public 이건 private이건 사용할 수 있습니다. 보통 앱을 만들 때는 Main 메소드를 다른 클래스에서 호출한다는 생각은 하지 않지만 가능은 하다는 것 입니다. 또 Main 함수의 재귀 호출도 가능합니다. 쓸일은 많이 없겠지만 일단 그렇습니다.

그 다음 static 지시자는 정적 메소드의 뜻입니다. 정적(static)의 뜻은 이 클래스가 로드되면 프로그램이 종료될 때 까지 메소드가 메모리에 존재한다는 뜻 입니다. 반대의 뜻은 동적(dynamic) 인데 정적 메소드를 제외하면 다른 메소드들은 호출이 끝나면 사라지니까 동적이라고 봐도 무방하지요. 해서 굳이 동적이라고 붙이지는 않는 것 같습니다. 정적(static) 과 동적(dynamic) 개념은 클래스, 메소드, 필드, 변수 등 적용 대상에 따라 의미가 달라지니 주의합니다. 여기서는 static 메소드는 처음부터 끝까지 메모리에 존재한다는 뜻으로 보면 됩니다. Main 에서 프로그램이 시작하고 Main 에서 프로그램이 끝나니까 static 상태를 유지해야 합니다.

또 인스턴스 없이 사용할 수 있는 메소드라서 CLR은 Program의 인스턴스를 생성할 필요가 없습니다. 아래 코드를 실행하면 Main 메소드가 실행된 후에 Program 클래스 인스턴스를 만드는데요. Main 메소드의 실행 전에 Program 인스턴스가 생성되지 않았습니다.

그래서 Main 메소드는 static을 사용합니다.

using System;

namespace MyApp
{
    public class Program
    {
        Program()
        {
            System.Console.WriteLine("Program instance created");
        }
        public static void Main(string[] args)
        {
            System.Console.WriteLine("Main Method started...");
            Program p1 = new Program();
        }
    }
}

[실행]

Main Method started...
Program instance created

다음 void는 리턴 형식입니다. 뜻은 호출한 측에게 아무것도 리턴할 것이 없다는 뜻입니다. 프로그램 종료시 CLR에 뭔가 전달하려면 int 반환값을 사용할 수도 있습니다. 윈도우에서 프로그램을 실행하고 종료할 경우 반환값은 환경변수에 저장되므로 운영체제 차원에서 프로그램의 종료상태를 체크할 수 있습니다. 반환값의 처리에 대해서는 MS의 문서를 참고하도록 합니다. (참고문서 MS)

보통은 코드가 다 실행되면 끝나니까 void 를 기본으로 두는 것이고요. 사실 운영체제가 개별앱의 종료 상태까지 알아야할 정도가 아니라면 어차피 내부에서 예외처리를 위한 exception 로직이 있기 때문에 초보단계에서 신경쓰지 않아도 됩니다.

마지막으로 string[] args 인데 가변 매개변수를 받습니다. C# 문법으로는 params 을 적용한 것과 같습니다. 문자열 배열로 들어가는데 dotnet 명령어로 실행하면 index 0에는 소스파일의 이름이 들어갑니다. 빌드된 exe 파일을 실행하면 소스코드는 매개변수로 전달이 안되는 약간의 차이가 있습니다.

using System;


namespace MyApp
{
    public class Program
    {
        public static int Main(string[] args)
        {
            System.Console.WriteLine("[Main Method started...]");

            System.Console.WriteLine(args.ToString());

            foreach (var item in args.Select((v,i)=>(v,i)))
            {
                System.Console.WriteLine($"{item.i}: {item.v}");
            }

            return 0;
        }
    }
}

[실행]

d:\ConsoleApp>dotnet run Program.cs item1 item2 item3
[Main Method started...]
System.String[]
0: Program.cs
1: item1
2: item2
3: item3

초보자 중에는 이 Main 메소드에 붙어있는 것들이 궁굼할 수가 있습니다. 쓸데없이 붙어있는 것 같지만 하나씩 다 의미가 있습니다. 이런 내용을 알아가는 것도 언어의 구조를 이해할 때 필요합니다.

요약

메소드의 기초와 처음부터 봐왔던 Main 함수에 대한 내용을 알아 봤습니다. 개인적으로 함수부터 배운 세대라서 객체지향 프로그래밍 부터 시작하는게 메소드를 이해하는데 더 도움이 될지 아니면 절차형 프로그래밍을 좀 배운 후에 하는게 맞는 건지는 잘 모르겠습니다. C#은 C 문법의 계승이란 것도 있어서 C나 C++을 먼저 배우면 좋겠지만 그것은 또 그 나름의 고충이 있기 때문에 어느 것이 맞다고 하기 힘든 것 같습니다.

최근에는 파이썬 같은 스크립트 언어도 그냥 함수와 데이터만으로 코딩하기도 하니까 굳이 C를 안해도 함수 개념을 잡는데 문제는 없을텐데 메소드는 클래스가 상속에 들어가면 복잡하기 때문에 쉽지는 않습니다.

OOP를 이해하는데 하나의 언어로 끝나지는 않겠지만, 초보자의 경우 여러개의 언어를 산만하게 학습하는 것 보다 한개의 언어로 기본 객체지향 프로그래밍을 확실하게 완료하는 쪽이 좋습니다. 그 다음에는 디자인 패턴 등을 공부하고 경험을 쌓을 수 있을 것 입니다.

그럼 어렵지만 메소드를 잘 이해하길 바랍니다.

참고문서 MS

Main() and command-line arguments | Microsoft Docs

Leave a Comment