Menu

C# 접근 한정자: public, private, protected, internal

C#의 여섯 가지 접근 한정자(public, private, protected, internal, protected internal, private protected), 각각이 허용하는 것, 아무것도 쓰지 않았을 때의 기본값, 그리고 이들이 만드는 컴파일러 오류를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

접근 한정자는 어떤 코드가 타입이나 멤버를 쓸 수 있는지 정합니다. 클래스는 이를 통해 다른 코드가 의존하는 부분(공개 표면)과 마음대로 바꿔도 되는 부분(내부)을 나눕니다. C#에는 여섯 가지 한정자가 있고, 아무것도 쓰지 않았을 때의 기본값이 있습니다.

여섯 가지 한정자

한정자포함하는 타입파생 타입, 같은 어셈블리다른 코드, 같은 어셈블리파생 타입, 다른 어셈블리다른 코드, 다른 어셈블리
public예예예예예
protected internal예예예예아니요
protected예예아니요예아니요
internal예예예아니요아니요
private protected예예아니요아니요아니요
private예아니요아니요아니요아니요

어셈블리는 컴파일된 프로젝트 하나, 즉 그 프로젝트가 만드는 .dll이나 .exe입니다. "같은 어셈블리"는 "같은 프로젝트"라는 뜻이며, 그래서 internal이 "내 코드에는 보이고 내 라이브러리 사용자에게는 숨겨짐"으로 동작합니다.

private과 public: 캡슐화

private은 멤버의 기본값이며 대부분의 필드에 맞는 선택입니다. 클래스는 호출하는 쪽에 필요한 것을 public 메서드와 속성으로 노출하고, 그 메서드들이 객체의 데이터를 일관되게 유지합니다.

출력:

False
True
Priya: 180

balance가 private이므로 이를 바꾸는 방법은 Deposit과 TryWithdraw뿐이고, 둘 다 규칙을 강제합니다. 프로그램의 다른 어떤 코드도, 어떻게 작성되었든 음수 잔액을 만들 수 없습니다. 이것이 캡슐화가 주는 것입니다. 규칙이 한 곳에 있습니다.

private은 객체 단위가 아니라 타입 단위입니다. Account의 메서드는 다른 Account 인스턴스의 other.balance를 읽을 수 있으며, Equals와 비교 메서드는 보통 이렇게 작성합니다.

protected: 파생 클래스에 보임

protected 멤버는 바깥 세상에는 숨겨지지만, 선언한 클래스를 상속하는 클래스에서는 쓸 수 있습니다.

출력:

Sam: 4000.00
Rita: 6400.00

사람들을 놀라게 하는 규칙이 하나 있습니다. Manager 안에서 this나 다른 Manager의 baseSalary는 쓸 수 있지만, 임의의 Employee의 것은 쓸 수 없습니다:

class Manager : Employee
{
    public decimal Compare(Employee other)
    {
        return baseSalary - other.baseSalary;
        // error CS1540: Cannot access protected member 'Employee.baseSalary' via a qualifier
        // of type 'Employee'; the qualifier must be of type 'Manager' (or derived from it)
    }
}

이유는 이렇습니다. other는 Employee에서 파생된 Contractor일 수도 있고, protected는 계층 구조에서 자기 가지에 대한 접근을 허락하지 형제 가지에 대한 접근을 허락하지 않습니다.

protected 필드는 모든 파생 클래스를 그 필드에 영원히 묶습니다. 많은 코드베이스는 protected 속성이나 메서드를 선호하거나, 필드는 private으로 두고 파생 클래스에는 필요한 것만 줍니다.

internal: 어셈블리 안에서 보임

internal 멤버와 타입은 같은 프로젝트의 모든 코드가 쓸 수 있고, 그 밖의 어떤 것도 쓸 수 없습니다. 라이브러리는 공개 클래스가 의존하는 헬퍼에 이를 씁니다:

// In the MyShop.Pricing library project
public class PriceCalculator
{
    public decimal Total(Cart cart) => TaxRules.Apply(cart.Subtotal);
}

internal static class TaxRules   // callers of the library cannot see this class
{
    internal static decimal Apply(decimal amount) => amount * 1.2m;
}

최상위 타입은 한정자를 쓰지 않으면 internal입니다. 그래서 클래스 라이브러리에서 public으로 표시하는 것을 잊은 클래스는 그것을 참조하는 프로젝트에서 "보이지 않습니다".

단위 테스트 프로젝트는 별도의 어셈블리이므로 internal 코드도 볼 수 없습니다. 표준 해결책은 라이브러리에 특성을 두는 것입니다:

[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("MyShop.Pricing.Tests")]

protected internal과 private protected

이 둘은 앞의 한정자들을 서로 반대 방식으로 조합하며, 이름이 헷갈리기 쉽습니다:

  • **protected internal**은 protected 또는 internal입니다. 같은 어셈블리의 모든 코드와 다른 어셈블리의 파생 클래스입니다. 더 넓은 쪽입니다.
  • private protected(C# 7.2)는 protected 이고 internal입니다. 같은 어셈블리에 있는 파생 클래스만입니다. 확장 지점을 라이브러리 안에 두어야 하는 기본 클래스를 위한 더 좁은 쪽입니다.
public class ReportBase
{
    protected internal string Title;      // same assembly, or subclasses anywhere
    private protected int RowLimit = 500; // subclasses in this assembly only (C# 7.2)
}

아무것도 쓰지 않았을 때의 기본값

위치기본값허용되는 것
네임스페이스에 선언한 타입internalpublic, internal, file(C# 11)
클래스의 멤버private여섯 가지 모두
구조체의 멤버privatepublic, internal, private(구조체는 상속할 수 없음)
중첩 타입private여섯 가지 모두
인터페이스 멤버publicC# 8부터 명시적 한정자 허용
열거형 멤버public없음

명시하는 데는 단어 하나면 되고 다음에 읽는 사람의 의문을 없애 주므로, 대부분의 스타일 가이드는 기본값과 같더라도 한정자를 쓰라고 합니다.

접근자 수준의 접근

속성이나 인덱서는 한 접근자에 속성 자체보다 좁은 한정자를 줄 수 있습니다:

출력:

report.pdf: 100%

접근자의 한정자는 속성의 것보다 더 제한적이어야 하고, 두 접근자 중 하나만 가질 수 있습니다.

일관성 없는 접근성

public 멤버는 자기보다 덜 보이는 타입을 노출할 수 없습니다. 호출하는 쪽이 이름조차 쓸 수 없는 것을 받게 되기 때문입니다:

internal class Discount { }

public class Checkout
{
    public Discount Current() => null;
    // error CS0050: Inconsistent accessibility: return type 'Discount' is less accessible than method 'Checkout.Current()'
}

같은 규칙이 매개변수 타입에는 CS0051을, 속성 타입에는 CS0053을 줍니다. 타입을 멤버만큼 보이게 하거나, 멤버를 타입만큼 숨겨서 해결하세요.

한정자 고르기

동작하는 가장 제한적인 한정자로 시작하고, 호출하는 쪽이 필요로 할 때만 넓히세요. 나중에 더 보이게 만드는 것은 쉽고 누구도 깨뜨리지 않지만, 덜 보이게 만들면 그것을 쓰던 모든 호출하는 쪽이 깨집니다. 실제로는 필드는 private, 클래스의 목적을 이루는 멤버는 public, 헬퍼는 private, 라이브러리 전용 타입은 internal입니다.

자주 묻는 질문

C#의 접근 한정자에는 어떤 것이 있나요?

여섯 가지입니다: public(모든 코드), private(포함하는 타입만), protected(포함하는 타입과 그로부터 파생된 타입), internal(같은 어셈블리의 모든 코드), protected internal(같은 어셈블리, 또는 어디서든 파생 타입), private protected(같은 어셈블리의 파생 타입, C# 7.2). C# 11은 한 소스 파일에서만 보이는 타입을 위한 file을 추가했습니다.

C#의 기본 접근 한정자는 무엇인가요?

클래스나 구조체의 멤버(필드, 메서드, 속성, 중첩 타입)는 기본적으로 private입니다. 최상위 타입(네임스페이스 안에 직접 선언한 클래스)은 기본적으로 internal입니다. 인터페이스 멤버와 열거형 멤버는 public입니다.

C#에서 protected는 무슨 뜻인가요?

protected 멤버는 자기 클래스 안과 그로부터 파생된 모든 클래스 안에서 보이지만, 다른 코드에는 보이지 않습니다. 파생 클래스는 자기 타입을 통해서만 이를 쓸 수 있습니다. Manager 안에서 this.baseSalary는 되지만, 다른 Employee 객체의 baseSalary에 접근하면 CS1540 오류입니다.

C#에서 internal은 무엇인가요?

internal은 타입이나 멤버를 같은 어셈블리(같은 프로젝트의 컴파일된 .dll이나 .exe)의 모든 코드에 보이게 하고, 그 밖에서는 보이지 않게 합니다. 라이브러리가 헬퍼 클래스를 공개 API에서 빼 두는 방법입니다. [assembly: InternalsVisibleTo("MyLib.Tests")]를 쓰면 테스트 프로젝트도 볼 수 있습니다.

"is inaccessible due to its protection level" 오류는 어떻게 해결하나요?

CS0122 오류로, 코드가 볼 수 없는 멤버를 쓰고 있다는 뜻입니다. 가장 흔하게는 기본값 private으로 남겨 둔 필드입니다. 외부 코드가 읽어야 한다면 필드를 그냥 public으로 만들지 말고 public 속성이나 메서드를 노출하세요. 다른 프로젝트의 타입이라면 아마 internal일 것입니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기