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

대부분의 C# 면접에서는 전체 엔터프라이즈 시스템을 구축하도록 요구하지 않습니다.
대신, OOP 기본 개념 뒤에 있는 멘탈 모델을 실제로 이해하고 있는지를 조용히 테스트합니다:
- 클래스를 정의하는 것과 객체를 생성하는 것의 차이는 무엇인가요?
- 메서드가 **동작(behavior)**을 나타내는 이유는?
- C#은 다중 클래스를 상속할 수 있나요?
- 생성자를 사용하는 이유는 무엇이며, 언제 객체 초기화자를 선호하나요?
- 접근 제한자(access modifiers)는 실제로 무엇을 제어하나요?
- 현대 C#에서
record타입이 “특별한” 이유는?
이번 포스트에서는 퀴즈 형식의 질문들을 프로덕션 패턴으로 전환하여 다음 상황에서 재사용할 수 있도록 합니다:
- 코드 리뷰
- 아키텍처 논의
- 기술 면접
- 실제 .NET 서비스
TL;DR — What you’ll learn
- The type vs. instance 정신 모델 (
classvs.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
recordtypes 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, 경계, 클린 아키텍처에 대한 사고 방식을 가르침.