C# · 심층 가이드
C# 객체지향 완전 정리
클래스와 구조체, record의 차이를 축으로 생성자, 상속, 인터페이스, 불변 객체, 빌더, 의존성 주입까지 C# 객체지향 설계 스무 가지를 코드로 비교합니다.
객체지향이라는 단어 하나로 묶기에 C#의 타입 선택지는 꽤 넓습니다. 참조로 오가는 class, 값으로 복사되는 struct, 그리고 값 동등성과 with 식을 기본으로 갖춘 record가 각각 다른 문제를 풉니다. 자바나 파이썬에서 넘어온 사람이 가장 자주 걸리는 부분도 여기입니다. 상속 계층을 어떻게 짤지 고민하기 전에, 이 타입이 정체성으로 비교되는지 값으로 비교되는지부터 정하는 편이 순서에 맞습니다.
기본 클래스 또는 구조체와 생성자와 초기화로 뼈대를 세운 다음, 불변 데이터 객체와 값 객체 비교를 이어서 보면 record가 왜 등장했는지 자연스럽게 드러납니다. 변경 불가능 업데이트는 그 연장선에서 기존 인스턴스를 건드리지 않고 새 값을 만드는 방식이고, 반대로 생성 인자가 많아 손이 무거워질 때 빌더 패턴이 대안이 됩니다. 상속 또는 확장과 합성으로 기능 묶기는 같은 요구를 다르게 푼 짝이니 나란히 두고 읽으면 좋습니다.
record의 with는 얕은 복사입니다. 안에 리스트나 배열이 들어 있으면 새 인스턴스와 원본이 같은 컬렉션을 가리키므로, 한쪽에서 항목을 추가하면 다른 쪽에도 보입니다. 불변이라 믿고 공유하다가 생기는 버그가 대부분 여기서 나옵니다. 확장 스타일 동작도 성질을 알아 둘 만합니다. 확장 메서드는 정적 메서드를 문법적으로 바꿔 부르는 것이라, 인스턴스가 null이어도 호출 자체는 성공하고 예외는 메서드 안에서 납니다.
01기본 클래스 또는 구조체
C#에서 기본 클래스 또는 구조체 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 기본 클래스 또는 구조체
using System;
public class User { public User(string name, int level) { Name = name; Level = level; } public string Name { get; } public int Level { get; } }
Console.WriteLine(new User("Ada", 1).Name);객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
02생성자와 초기화
C#에서 생성자와 초기화 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 생성자와 초기화
using System;
public record Profile(string Name, string[] Tags);
Console.WriteLine(new Profile("Ada", new[] { "cs" }));객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
03인스턴스 메서드
C#에서 인스턴스 메서드 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 인스턴스 메서드
using System;
public class User { public User(string name, int level) { Name = name; Level = level; } public string Name { get; } public int Level { get; } }
Console.WriteLine(new User("Ada", 3).Name);객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
04정적 메서드
C#에서 정적 메서드 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 정적 메서드
using System;
public record Profile(string Name, string[] Tags);
Console.WriteLine(new Profile("Ada", new[] { "cs" }));객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
05상속 또는 확장
C#에서 상속 또는 확장 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 상속 또는 확장
using System;
public class User { public User(string name, int level) { Name = name; Level = level; } public string Name { get; } public int Level { get; } }
Console.WriteLine(new User("Ada", 5).Name);객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
06인터페이스 또는 trait
C#에서 인터페이스 또는 trait 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 인터페이스 또는 trait
using System;
public record Profile(string Name, string[] Tags);
Console.WriteLine(new Profile("Ada", new[] { "cs" }));객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
07캡슐화
C#에서 캡슐화 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 캡슐화
using System;
public class User { public User(string name, int level) { Name = name; Level = level; } public string Name { get; } public int Level { get; } }
Console.WriteLine(new User("Ada", 7).Name);객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
08합성으로 기능 묶기
C#에서 합성으로 기능 묶기 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 합성으로 기능 묶기
using System;
public record Profile(string Name, string[] Tags);
Console.WriteLine(new Profile("Ada", new[] { "cs" }));객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
09팩터리 메서드
C#에서 팩터리 메서드 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 팩터리 메서드
using System;
public class User { public User(string name, int level) { Name = name; Level = level; } public string Name { get; } public int Level { get; } }
Console.WriteLine(new User("Ada", 9).Name);객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
10열거형 상태 표현
C#에서 열거형 상태 표현 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 열거형 상태 표현
using System;
public record Profile(string Name, string[] Tags);
Console.WriteLine(new Profile("Ada", new[] { "cs" }));객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
11불변 데이터 객체
C#에서 불변 데이터 객체 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 불변 데이터 객체
using System;
public class User { public User(string name, int level) { Name = name; Level = level; } public string Name { get; } public int Level { get; } }
Console.WriteLine(new User("Ada", 11).Name);객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
12빌더 패턴
C#에서 빌더 패턴 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 빌더 패턴
using System;
public record Profile(string Name, string[] Tags);
Console.WriteLine(new Profile("Ada", new[] { "cs" }));객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
13다형성
C#에서 다형성 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 다형성
using System;
public class User { public User(string name, int level) { Name = name; Level = level; } public string Name { get; } public int Level { get; } }
Console.WriteLine(new User("Ada", 13).Name);객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
14의존성 주입
C#에서 의존성 주입 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 의존성 주입
using System;
public record Profile(string Name, string[] Tags);
Console.WriteLine(new Profile("Ada", new[] { "cs" }));객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
15값 객체 비교
C#에서 값 객체 비교 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 값 객체 비교
using System;
public class User { public User(string name, int level) { Name = name; Level = level; } public string Name { get; } public int Level { get; } }
Console.WriteLine(new User("Ada", 15).Name);객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
16저장소 객체
C#에서 저장소 객체 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 저장소 객체
using System;
public record Profile(string Name, string[] Tags);
Console.WriteLine(new Profile("Ada", new[] { "cs" }));객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
17서비스 객체
C#에서 서비스 객체 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 서비스 객체
using System;
public class User { public User(string name, int level) { Name = name; Level = level; } public string Name { get; } public int Level { get; } }
Console.WriteLine(new User("Ada", 17).Name);객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
18DTO 매핑
C#에서 DTO 매핑 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// DTO 매핑
using System;
public record Profile(string Name, string[] Tags);
Console.WriteLine(new Profile("Ada", new[] { "cs" }));객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
19변경 불가능 업데이트
C#에서 변경 불가능 업데이트 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 변경 불가능 업데이트
using System;
public class User { public User(string name, int level) { Name = name; Level = level; } public string Name { get; } public int Level { get; } }
Console.WriteLine(new User("Ada", 19).Name);객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
20확장 스타일 동작
C#에서 확장 스타일 동작 개념을 코드로 빠르게 확인하는 객체지향 예제입니다.
C# code
// 확장 스타일 동작
using System;
public record Profile(string Name, string[] Tags);
Console.WriteLine(new Profile("Ada", new[] { "cs" }));객체 예제는 데이터와 동작이 어떻게 묶이는지에 집중하면 이해가 빨라집니다.
객체 하나에 너무 많은 책임을 넣으면 구조가 빠르게 무거워집니다.
정리하며
- 타입을 고를 때는 상속 구조보다 정체성 비교인지 값 비교인지를 먼저 정합니다
- record의
with는 얕은 복사라 내부 컬렉션은 원본과 계속 공유됩니다 - 확장 메서드는 정적 호출이라
null인스턴스에서도 호출 자체는 성공합니다 - 생성자 인자가 늘어나면 기본값 남발 대신 빌더나 설정 객체로 옮겨 담습니다
더 깊이 들어가고 싶다면 C# 학습 라이브러리에서 다른 주제 가이드를 이어서 보거나, 언어 비교에서 같은 개념이 다른 언어에서 어떻게 표현되는지 확인해 보세요.