C#에서는 정의하는 형식을 사용하는 범위나 형식 내의 멤버에 대해 사용할 수 있는 범위를 접근 한정자를 통해 지정할 수 있습니다.
형식 정의문 앞에 올 수 있는 접근 한정자에는 public과 internal이 있는데 명시를 하지 않으면 internal로 지정됩니다. public으로 접근 지정하면 다른 .NET 어셈블리에서도 접근할 수 있지만 internal로 지정하면 같은 .NET 어셈블리에서만 접근할 수 있습니다. 예를 들어 라이브러리를 만들 때 라이브러리 내에서만 접근할 멤버들은 internal로 외부에서 사용해도 되는 멤버는 public으로 지정합니다. 이에 대해 이해하려면 여러 개의 .NET 어셈블리로 구성된 프로그램을 만들 수 있어야 하는데 여기서 이를 다루는 것은 적절치 않은 것 같습니다. 이에 관한 내용은 .NET 어셈블리와 라이브러리에서 다시 다루겠습니다.
형식 내의 멤버에 대한 접근 한정자는 public, preocted, private, internal, protected internal이 있습니다. 멤버에 접근 한정자가 명시되어 있지 않으면 private으로 지정됩니다. private은 해당 형식 내부에서만 접근할 수 있게 함으로써 잘못된 사용을 차단합니다. 이에 반해 public은 모든 곳에서 접근할 수 있으므로 외부 .NET 어셈블리에서도 접근해도 되는 멤버에만 지정합니다. 그리고 같은 .NET 어셈블리 내에 있지만, 형식 외부에서 접근해도 되는 멤버는 internal로 지정합니다. protected는 파생 클래스 형식에서는 접근해도 되지만 다른 형식에서는 사용하면 안 되는 멤버를 지정합니다. 그리고 같은 .NET 어셈블리와 파생된 형식에서 접근할 수 있게 하려면 protected internal로 지정합니다.
여기에서는 접근 한정을 private와 internal로 하였을 때의 차이를 통해 정보 은닉을 통해 데이터의 신뢰성을 높이는 수단으로 사용할 수 있다는 것에 대해 다룰 것입니다. 접근 한정자 proctected에 대한 내용은 6장 상속과 다형성에서 다룰 것이며 public에 대한 부분은 10장 .NET 어셈블리에서 다루겠습니다. 참고로, C++에서 제공되는 접근 한정자 public은 C#에서 제공하는 internal과 같은 수준의 접근 한정자이며 protected와 private은 동일하다고 볼 수 있습니다.
[그림 20]은 두 개의 .NET 어셈블리로 작성된 프로그램의 컴포넌트 다이어그램을 통해 접근 한정자의 접근 가능 여부를 도식한 것입니다. 두 개의 컴포넌트(A,B)는 각각 .NET 어셈블리이며 A 컴포넌트에는 ClassA가 있으며 B컴포넌트에는 ClassB, ClassC, ClassD가 정의되어 있습니다. 먼저, public은 다른 어셈블리에 있는 것을 사용할 수 있습니다. internal은 같은 어셈블리 내에 있는 다른 형식에서 접근할 수 있습니다. protected는 파생 클래스에서 접근할 수 있으며 private은 형식 내부에서만 접근할 수 있습니다.
모든 멤버에 대한 접근 한정을 internal로 지정하였을 때 어떠한 위험이 있는지에 대해 살펴봅시다. 예를 들어 프로그램의 학생(Student) 개체에 공부를 시키면 공부를 한 시간만큼 아이큐(iq)가 올라가게 할게요. 단, 학생의 아이큐는 최대 200까지 올라갈 수 있습니다. 이처럼 동작할 수 있는 학생을 클래스로 정의를 다음과 같이 모든 멤버를 internal로 지정하는 것은 어떨까요?
▶ 모든 멤버를 internal로 접근 지정
class Student { internal const int max_iq = 200; internal int iq = 100; internal void Study(int scnt) { if ((iq + scnt) > max_iq) { iq = max_iq; } else { iq += scnt; } } } class Program { static void Main(string[] args) { Student stu = new Student(); Console.WriteLine("공부할 시간을 입력하세요."); int scnt = int.Parse(Console.ReadLine()); stu.iq += scnt; //멤버 필드 iq를 직접 변경 Console.WriteLine("아이큐:{0}", stu.iq); } }
이와 같이 학생을 클래스로 정의하면 사용하는 곳에서 Study 메서드를 이용하지 않고 직접 iq를 변경할 수 있습니다. 이러면 사용하는 곳에서 제대로 사용한다면 문제되지 않지만 그렇지 않으면 프로그램에 버그가 존재하게 될 것입니다.
이처럼 사용한다고 가정했을 때 사용하는 곳에서 Study 메서드를 이용하지 않고 직접 학생 개체의 iq 멤버 필드값을 조절하면 최대 아이큐(200)를 넘을 수 있습니다.
▶ 120을 입력하였을 때 실행 결과
공부할 시간을 입력하세요. 120 아이큐:220
이 경우에 학생 클래스를 정의한 곳과 사용한 곳 중에 어디서 잘못한 것일까요? C#과 같은 OOP 언어에서는 위 같은 경우에 학생 클래스를 정의하는 곳에서 각 멤버마다 접근 한정을 지정할 수 있으므로 학생 클래스를 정의한 곳에서 잘못한 것이라 볼 수 있습니다.
다음은 학생 클래스에 각 멤버의 접근 한정을 필요한 수준으로 변경하여 신뢰성을 높인 예입니다.
▶ 필요한 수준으로 접근 지정하여 신뢰성을 높인 예
class Student { const int max_iq = 200; int iq = 100; //접근 수준: private internal int Iq { get //접근 수준: internal { return iq; } private set //접근 수준 private { if ((iq + value) > max_iq) { iq = max_iq; } else { iq += value; } } } internal void Study(int scnt) //접근 수준: internal { Iq += scnt; } } class Program { static void Main(string[] args) { Student stu = new Student(); Console.WriteLine("공부할 시간을 입력하세요."); int scnt = int.Parse(Console.ReadLine()); stu.Study(scnt); //접근수준이 private인 멤버 필드 iq를 직접 사용 못함 //접근 수준이 internal인 멤버 속성 Iq를 통해 개체의 iq를 얻어옴 Console.WriteLine("아이큐:{0}", stu.Iq); } }
▶ 실행 결과
공부할 시간을 입력하세요. 120 아이큐:200
먼저, iq에 대한 형식 외부에서 직접적인 사용을 막기 위해 private으로 지정하였습니다(접근 한정자를 명시하지 않았을 때는 private으로 지정됨). 물론, 학생 개체를 원하는 시간만큼 공부시킬 수 있게 Study 메서드는 여전히 internal입니다. 그리고 사용하는 곳에서 학생 개체의 iq를 확인할 수 있게 멤버 속성 Iq를 두었고 get 블록과 set 블록의 접근 한정을 비대칭으로 지정하였습니다. get은 속성 Iq에 지정한 internal이므로 학생 클래스 외부에서 접근할 수 있지만, set은 private으로 명시하였기 때문에 외부에서 접근하지 못하게 됩니다. 이처럼 접근 한정자를 통해 필요한 수준으로 정보 은닉하여 신뢰성을 높일 수 있습니다. 이를 통해 개발 단계에서 버그를 줄일 수 있을 것입니다.
개발 시에 적절하게 접근 한정자를 지정하는 것은 많은 노하우가 필요합니다. 초기 개발자는 될 수 있으면 접근 한정자는 디폴트를 사용하세요. 그리고 반드시 접근이 필요한 곳이 생기면 그 위치에서 접근할 수 있을 정도로 접근 수준을 변경하세요. 이 같은 습관을 갖는다면 보다 신뢰성 있는 프로그램을 제작하실 수 있을 것입니다. 그리고 멤버 필드는 언제나 접근 한정자를 디폴트를 사용하시고 필요한 경우에는 해당 멤버 필드에 대해 접근 가능한 멤버 속성이나 멤버 메서드를 제공하십시오.