불변 ( Immutable )
프로그래밍을 하다 보면 한 번 만들어진 데이터가 절대 바뀌지 않는 경우들을 보게 된다.
문자열을 조작하려고 했는데 원본이 그대로인 경우가 발생하는데 이런 현상이 바로 불변 특성 때문이다
정의
불변은 "한 번 생성된 객체의 상태가 절대 변경되지 않는다" 는 특성이다.
객체가 만들어진 후에는 내부 데이터를 수정할 수 없고 , 수정하려고 하면 새로운 객체가 생성된다.
대표적인 불편 타입으로는 string , delegate 그리고 대부분의 값 타입들이 있다.
예시 코드
string original = "HoChan";
string modified = original + " Blog";
Console.WriteLine(original); // "HoChan" (원본 그대로)
Console.WriteLine(modified); // "HoChan Blog" (새로운 문자열)
여기서 + 연산을 했다고 해서 original 이 바뀌는 것이 아니다.
새로운 문자열 객체가 만들어져서 modified 에 할당된 것이다.
string msg = "Ho";
msg += "Chan";
Console.WriteLine(msg);
// 출력: HoChan
겉으로 보기에는 msg가 수정된 것처럼 보인다.
실제로는 이렇게 돌아간다
- "Chan" 이라는 새로운 문자열이 Heap 에 생성된다
- msg 변수가 새로운 문자열을 참조하게 된다
- 기존 "Ho" 문자열은 GC 컬렉션의 대상이 된다
기업 면접에서 자주 나오는 문제가 있다.
internal class Program
{
static void Main(string[] args)
{
string a = "안녕";
string b = a;
b = "하세요";
Console.WriteLine(a + b);
}
}
정답을 바로 말하면 "안녕하세요" 가 출력된다.
핵심 개념 : string 은 불변 ( Immutable )
- string b = a; → a 와 b 는 같은 문자열 객체 "안녕"을 참조한다
- 하지만 불변이므로 값을 "수정"하는 것이 불가능하다
- b = "하세요"; → 기존 문자열을 바꾸는 것이 아니라
└ b 가 새로운 문자열 객체를 참조하도록 변경된다
a → 여전히 "안녕"
b → "하세요" 새 객체
delegate 의 불변 특성
델리게이트도 불변 객체이다.
델리게이트에 함수를 추가하거나 제거할 때에도 기존 델리게이트가 바뀌는 것이 아니라 새로운 델리게이트가 생성된다.
Action printMessage = () => Console.WriteLine("Hello");
Action original = printMessage;
printMessage += () => Console.WriteLine("World");
printMessage();
original();
- += 연산으로 함수를 추가했지만 , original 과 printMessage 는 서로 다른 델리게이트 객체를 참조하고 있다
▼ printMessage 호출 시
Hello
World
▼original 호출 시
Hello
- original = printMessage; 할 때
original 은 딱 "Hello" 한 개만 들어있는 델리게이트를 가리킨다 - printMessage += ... 는 printMessage 에 새로운 델리게이트를 추가한 것처럼 보이지만
사실상 새로운 델리게이트 객체를 만들어서 printMessage 에 다시 대입한 것이다
불변 특성의 장점
1. 예측 가능성 증가 ( 버그 크게 감소 )
값이 중간에 몰래 바뀌는 일이 없다
그러므로 디버깅 스트레스가 크게 줄어든다
코드 흐름이 명확해져 유지보수가 쉽다
2. 쓰레드 안전 ( Thread - Safe )
읽기만 가능해서 여러 쓰레드가 동시에 접근해도 안전하다
동기화 ( lock ) 절차가 줄어들어 멀티쓰레드 환경에서 매우 유리하다
3. 해시 코드 안정성
Dictionary<TKey , TValue> 의 키로 사용할 때 안전하다
4. 참조 공유가 안전하다
가변 객체는 여러 곳에서 같은 객체를 참조하면 한 곳의 수정이 전체에 영향
불변 객체는 수정이 불가능해서 이런 문제가 없다.
불변 특성의 단점
1. 자주 변경하는 경우 오히려 비효율적이다
값 변경은 새로운 객체를 생성한다
문자열을 계속 누적하는 경우 매번 새 객체를 생성해야 한다
이 때문에 StringBuilder 가 필요하다
2. 메모리 사용량 증가 가능
변경이 필요할 때마다 새 객체를 생성한다. 특히 큰 객체라면 비효율이다
빈번한 문자열 조작 시 많은 임시 객체를 생성한다
GC 압박 증가 가능성이 커지고 성능 저하로 이어진다
해결 방법 : StringBuilder
문자열을 자주 수정해야 한다면 StringBuilder 를 사용하는 것이 좋다
StringBuilder sb = new StringBuilder();
sb.Append("Hello");
sb.Append(" ");
sb.Append("World");
string result = sb.ToString();
정리
- 불변은 안정성, 예측 가능성, 쓰레드 안전성이 매우 뛰어나다.
- 하지만 변경이 많은 환경에서는 오히려 성능이 떨어지고 객체 생성이 많아진다.
- 안정성이 필요한 영역은 불변 / 성능, 변경 빈도가 높으면 가변을 선택하면 된다.
참고 자료
https://learn.microsoft.com/ko-kr/dotnet/api/system.text.stringbuilder?view=net-10.0
'⭐C Sharp > 11. 객체 지향' 카테고리의 다른 글
| sealed class (0) | 2025.10.09 |
|---|---|
| virtual 과 override (0) | 2025.10.08 |
| readonly (0) | 2025.10.02 |
| 상속 ( OOP ) (0) | 2025.09.28 |
| 추상 클래스 ( Abstract Class ) (0) | 2025.09.28 |