C# OOP 마스터리 — 퀴즈 답변에서 프로덕션 수준 멘탈 모델까지

발행: (2025년 12월 25일 오전 12:47 GMT+9)
16 분 소요
원문: Dev.to

Source: Dev.to

C# OOP Mastery — From Quiz Answers to Production‑Grade Mental Models

대부분의 C# 면접에서는 전체 엔터프라이즈 시스템을 구축하도록 요구하지 않습니다.
대신, OOP 기본 개념 뒤에 있는 멘탈 모델을 실제로 이해하고 있는지를 조용히 테스트합니다:

  • 클래스를 정의하는 것객체를 생성하는 것의 차이는 무엇인가요?
  • 메서드가 **동작(behavior)**을 나타내는 이유는?
  • C#은 다중 클래스를 상속할 수 있나요?
  • 생성자를 사용하는 이유는 무엇이며, 언제 객체 초기화자를 선호하나요?
  • 접근 제한자(access modifiers)는 실제로 무엇을 제어하나요?
  • 현대 C#에서 record 타입이 “특별한” 이유는?

이번 포스트에서는 퀴즈 형식의 질문들을 프로덕션 패턴으로 전환하여 다음 상황에서 재사용할 수 있도록 합니다:

  • 코드 리뷰
  • 아키텍처 논의
  • 기술 면접
  • 실제 .NET 서비스

TL;DR — What you’ll learn

  • The type vs. instance 정신 모델 (class vs. new)
  • 메서드가 동작이며, 단순히 “함수”가 아니라는 이유
  • 실제 규칙: 단일 클래스 상속, 다중 인터페이스
  • 생성자, 객체 초기화자, 그리고 불변 조건
  • 속성을 이용한 캡슐화: get; set; (그리고 선임 개발자들이 setter를 제한하는 방법)
  • 접근 제한자: “무엇이 존재하는가”가 아니라 누가 접근할 수 있는가
  • protected 현실 (그리고 언제 디자인 냄새가 되는지)
  • record가 존재하는 이유: 값 동등성, 불변성, 그리고 데이터 모델링

1) “어떤 키워드가 객체를 생성하나요?” — 정의 vs. 인스턴스화

퀴즈 버전

“C#에서 객체를 생성하기 위한 키워드 또는 지시어는 무엇인가요?”

답: new

시니어 사고 모델

  • class 타입을 정의합니다 (청사진)
  • new 인스턴스를 생성합니다 (관리 힙에 할당되는 런타임 객체이며, 생성자를 통해 초기화됩니다)
public class Person
{
    public string Name { get; }
    public Person(string name) => Name = name;
}

var p = new Person("Carlos"); // ✅ 객체(인스턴스)를 생성합니다

인터뷰 발언

class는 청사진을 정의하고, new는 실제 인스턴스를 만들며 생성자를 실행합니다.”

2) “What do methods define inside a class?” — Behavior

Quiz version

“What do methods define within a class?”

Answer: Behavior

Why this matters in production

  • Properties/fields represent state.
  • Methods represent behavior—the rules for changing that state.
public sealed class BankAccount
{
    public decimal Balance { get; private set; }

    public void Deposit(decimal amount)
    {
        if (amount > 0)
        {
            Balance += amount;
        }
        // else ignore or throw
    }
}

“Methods encode business rules. Properties hold data. Behavior protects invariants.”

3) “C#는 하나의 인터페이스만 구현할 수 있다” — 거짓

Quiz version

“C#는 하나의 인터페이스만 구현할 수 있다.”

Answer: 거짓

Real rule (must‑memorize)

  • ✅ A class can implement multiple interfaces. → 클래스는 여러 인터페이스를 구현할 수 있다.
  • ❌ A class can inherit from only one base class. → 클래스는 하나의 기본 클래스만 상속할 수 있다.
public sealed class ExportService : IDisposable, IAsyncDisposable, IHostedService
{
    public void Dispose() { /*...*/ }
    public ValueTask DisposeAsync() => ValueTask.CompletedTask;
    public Task StartAsync(CancellationToken ct) => Task.CompletedTask;
    public Task StopAsync(CancellationToken ct) => Task.CompletedTask;
}

Interview soundbite

“Single inheritance for classes, multiple inheritance for interfaces.” → “클래스는 단일 상속, 인터페이스는 다중 상속.”

Source:

4) “생성 시에 속성을 어떻게 초기화할까?” — 생성자 (및 관련 기능)

퀴즈 버전

“객체를 생성할 때 속성을 초기화하려면 어떻게 해야 할까?”

답변: 생성자를 사용한다

시니어 뉘앙스: 생성자 vs. 객체 초기화 구문

두 방법 모두 값을 초기화할 수 있지만, 목적이 다릅니다.

✅ 생성자 = 불변 조건 보장

객체가 유효한 상태로 생성되어야 할 때 사용합니다.

public sealed class Order
{
    public Guid Id { get; }
    public string CustomerEmail { get; }

    public Order(Guid id, string customerEmail)
    {
        if (string.IsNullOrWhiteSpace(customerEmail))
            throw new ArgumentException("Email required.", nameof(customerEmail));

        Id = id;
        CustomerEmail = customerEmail;
    }
}

✅ 객체 초기화 구문 = 선택적 필드에 대한 편의성

DTO나 옵션 묶음에 적합합니다.

var opts = new HttpClientOptions
{
    Timeout = TimeSpan.FromSeconds(10),
    Retries = 3
};

면접 답변 예시

“생성자는 불변 조건을 설정합니다. 객체 초기화 구문은 선택적 설정을 편리하게 해줍니다.”

5) “클래스를 다른 클래스의 속성 타입으로 사용할 수 있다” — Composition

Answer: True

This is composition: a has‑a relationship (often better than inheritance).
이는 합성입니다: has‑a 관계(종종 상속보다 더 좋음).

public sealed class Address
{
    public string Street { get; init; } = "";
}

public sealed class Person
{
    public Address Address { get; init; } = new();
}

Why seniors prefer composition

시니어가 합성을 선호하는 이유

  • Inheritance creates tight coupling.
    상속은 강한 결합을 생성합니다.
  • Composition keeps boundaries cleaner and enables substitution.
    합성은 경계를 더 깔끔하게 유지하고 대체를 가능하게 합니다.

Interview soundbite
인터뷰 발언

“Prefer composition: it’s easier to refactor and test than deep inheritance chains.”
“합성을 선호하세요: 깊은 상속 체인보다 리팩터링과 테스트가 더 쉽습니다.”

6) “Access modifiers define the …” (content truncated in the original)

Quiz version

“Access modifiers define the visibility of members.”

Answer: True

What access modifiers actually control

Modifier접근 가능한 위치
public어디서든
internal같은 어셈블리
protected파생 타입 (어셈블리와 무관)
protected internal파생 타입 또는 같은 어셈블리
private protected같은 어셈블리 내 파생 타입
private포함 타입만

Senior perspective

  • 캡슐화누가 멤버를 사용할 수 있는가에 관한 것이며, 무엇이 존재하는가에 관한 것이 아니다.
  • 멤버를 과도하게 노출(public)하면 설계 냄새가 될 수 있다.
  • 필요한 사용을 만족하면서 가장 제한적인 접근 제한자를 사용하는 것이 바람직하다.
public abstract class Shape
{
    // Only derived types can read/write the radius.
    protected double Radius { get; set; }

    // Only this class can change the ID after construction.
    public Guid Id { get; private set; } = Guid.NewGuid();

    // Internal helper used by the assembly but hidden from consumers.
    internal void Validate() { /*...*/ }
}

Interview soundbite

“접근 제한자는 누가 멤버와 상호작용할 수 있는지를 정의하는 계약이며, 멤버의 존재 여부를 설명하는 것이 아니다.”

7) “Why do record types exist?” — Value‑equality & immutability

Quiz version

“What makes record types special in modern C#?”

Answer: 값 동등성, 내장된 불변성, 그리고 간결한 데이터 모델링

주요 장점

  • 값 기반 Equals/GetHashCode 가 자동으로 생성됩니다.
  • with 표현식 으로 비파괴적 변형을 할 수 있습니다.
  • DTO도메인 모델 을 위한 간결한 문법.
public record PersonDto(string FirstName, string LastName);
var p1 = new PersonDto("Alice", "Smith");
var p2 = p1 with { LastName = "Johnson" }; // 새로운 불변 인스턴스 생성
bool same = p1 == new PersonDto("Alice", "Smith"); // true – 값 동등성

Interview soundbite

“불변이며 값 의미를 갖는 데이터 전달 객체가 필요할 때 record 를 사용하고, 정체성 의미를 갖는 엔터티에는 사용하지 마세요.”

마무리 생각

이러한 OOP 기본 개념의 이유 를 이해하면:

  • 불변 조건을 강제하고 상태를 보호하는 코드를 작성할 수 있습니다.
  • 올바른 상속 vs. 컴포지션 전략을 선택할 수 있습니다.
  • 코드 리뷰면접에서 명확하게 의사소통할 수 있습니다.

이 사고 모델을 마스터하면 퀴즈 답변을 넘어 견고하고 프로덕션 수준의 .NET 애플리케이션을 구축할 수 있게 됩니다.

Source:

C# 인터뷰 퀵‑체크

1️⃣ “클래스는 속성과 메서드를 가진다” — 거짓

정답: 거짓

현실
접근 제한자는 존재 여부가 아니라 가시성을 정의합니다.

public sealed class User
{
    public string Email { get; private set; } = "";
    private string PasswordHash { get; set; } = "";
}

인터뷰 한 마디
“접근 제한자는 ‘누가 이 멤버를 건드릴 수 있는가?’에 답하고, ‘존재하는가?’에 답하지 않는다.”

2️⃣ “C#에서 여러 클래스를 상속받을 수 있나요?” — 아니오

정답: 한 클래스만

public class Employee : Person // ✅
{
}

C#에서 “다중 상속”을 모델링하는 방법

  • 인터페이스를 사용해 기능 정의
  • **구성(Composition)**을 사용해 동작 재사용
  • 데코레이터위임을 사용해 횡단 관심사 처리

인터뷰 한 마디
“C#는 다중 클래스 상속을 피한다—인터페이스와 구성을 조합하면 다이아몬드 문제 없이 유연성을 얻을 수 있다.”

3️⃣ “속성에 값을 할당하는 기호는?” — =

정답: =

person.Name = "Juan"; // 할당 연산자

시니어 개발자는 할당이 불변 조건이 깨지는 경우가 많기 때문에 신경 씁니다.
그래서 흔히 보이는 패턴:

  • private set;
  • init;
  • 안전하게 변형하는 메서드

4️⃣ protected 접근 범위 — 참 (하지만 신중히 사용)

정답:

protected는:

  • 선언한 타입 내부에서 접근 가능
  • 파생 타입 내부에서 접근 가능
public class Base
{
    protected int Value;
}

public class Derived : Base
{
    public void Set() => Value = 10; // ✅ 허용
}

시니어 경고
protected는 파생 클래스가 내부 세부 사항에 의존하게 만들어 설계가 새는 경우가 있다.
다음 상황에서만 사용하라:

  • 기본 클래스가 명시적으로 확장을 위해 설계된 경우
  • 상속 계층을 직접 제어할 수 있는 경우
  • 안정적이고 문서화된 템플릿‑메서드 모델이 있는 경우

5️⃣ 레코드가 값 동등성 때문에 중요한가? —

정답:

record가 존재하는 이유

레코드는 데이터 모델링을 위해 설계되었습니다:

  • 값 기반 동등성
  • 쉬운 불변성
  • 구조적 비교

DTO·이벤트·도메인 메시지에 최적.

public record Person(string Name, int Age);

var p1 = new Person("Ana", 30);
var p2 = new Person("Ana", 30);

Console.WriteLine(p1 == p2); // ✅ true (값 동등성)

인터뷰 한 마디
“레코드는 값 의미를 갖는 데이터에 사용한다—DTO와 도메인 이벤트에 아주 적합하다.”

6️⃣ 인터페이스 키워드 — interface

public interface ILogger
{
    void Log(string message);
}

7️⃣ 클래스 키워드 — class

public class Order { }

8️⃣ “OOP의 기본 개념” — 클래스와 객체

정답: class와 object

그 외 모든 개념(상속, 다형성, 추상화, 캡슐화)은 이 기반 위에 구축됩니다.

9️⃣ C#에서 다형성 — 오버라이딩 (및 인터페이스)

정답: Overwrite

public abstract class Animal
{
    public abstract string Speak();
}

public sealed class Dog : Animal
{
    public override string Speak() => "Bark";
}

🔟 추상화 문장 —

정답:

추상화는 무엇을 할지 정의하고, 어떻게 할지는 명시하지 않는다:

  • 인터페이스
  • 추상 클래스

1️⃣1️⃣ “속성을 정의할 때 고려해야 할 사항?” — 접근 수준, 타입, 이름

정답: A. 접근 수준, 타입 및 이름

public string Name { get; private set; } = "";

1️⃣2️⃣ 캡슐화 키워드 — get; set;

정답: get; set;

시니어 업그레이드
캡슐화는 “public get/set을 어디든”이 아니라 변경을 제어하는 것이다.

public sealed class Customer
{
    public string Email { get; private set; }

    public Customer(string email)
    {
        // 검증 및 정규화
        Email = email.Trim().ToLowerInvariant();
    }

    public void ChangeEmail(string newEmail)
    {
        Email = newEmail.Trim().ToLowerInvariant();
    }
}

It looks like the text you’d like translated didn’t come through. Could you please resend the content (including the source line) so I can provide the Korean translation?

최종 생각 — 왜 이러한 “기본” 답변이 중요한가

이 질문들은 퀴즈가 아니다. 직접적으로 다음과 연결된다:

  • SOLID (캡슐화, 추상화, 대체 가능성)
  • Clean Architecture (경계, 구성)
  • Dependency Injection (인터페이스, 객체‑그래프 생성)
  • Maintainability (불변 조건 및 제어된 변이)

이러한 정신 모델을 명확히 설명할 수 있다면, 단순히 “C#을 안다”는 것이 아니라 변화에 견디는 시스템을 구축할 수 있는 엔지니어처럼 사고하는 것이다.

✍️ 작성자: Cristian Sifuentes — 탄력적인 .NET 시스템을 구축하고, 팀에게 OOP, 경계, 클린 아키텍처에 대한 사고 방식을 가르침.

Back to Blog

관련 글

더 보기 »

C++에서 다중 상속의 생성자

Constructor: 생성자는 클래스와 같은 이름을 가진 클래스 멤버 함수이며, 주요 역할은 클래스 객체를 초기화하는 것이다. 생성자는 자동으로 호출된다.