dynamic은 C#에서 멤버 확인을 컴파일 타임이 아닌 런타임에 지연하는 키워드입니다. 내부적으로 .NET의 DLR(Dynamic Language Runtime)을 사용해 호출 규칙을 바인딩하고 캐시합니다. 정적 타입 안정성을 유지하면서도 COM, 스크립팅, 느슨한 데이터(예: JSON)와 상호 운용할 때 실용적으로 사용할 수 있습니다.
1. dynamic 한 줄 정의
dynamic은 컴파일러의 타입 검사를 건너뛰고, 실제 실행 시점의 객체 타입에 따라 멤버(필드, 속성, 메서드, 인덱서)를 찾습니다. 멤버가 없으면 RuntimeBinderException이 발생합니다.
2. dynamic vs var vs object
var는 컴파일 타임에 실제 타입으로 결정됩니다. object는 박싱된 최상위 타입이므로 멤버를 쓰려면 캐스팅이 필요합니다. dynamic은 컴파일 타임 검사를 미루고, 멤버 호출을 런타임에 해결합니다.
var s = "hello"; // 컴파일 타임: string
object o = "hello"; // 멤버 사용 전 캐스팅 필요
dynamic d = "hello"; // 멤버 사용을 런타임에 확인
Console.WriteLine(s.Length); // OK
// Console.WriteLine(o.Length); // 컴파일 오류
Console.WriteLine(((string)o).Length); // 캐스팅 후 OK
Console.WriteLine(d.Length); // 런타임에 확인3. 기본 사용과 런타임 바인딩
존재하지 않는 멤버를 호출하면 런타임에 예외가 발생합니다. 예외 타입은 Microsoft.CSharp.RuntimeBinder.RuntimeBinderException입니다.
try
{
dynamic any = 42;
Console.WriteLine(any.ToUpper()); // int에는 ToUpper가 없음 → 예외
}
catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException ex)
{
Console.WriteLine(ex.Message);
}dynamic이 식에 섞이면 결과도 dynamic이 됩니다. 의도치 않은 전파를 피하려면 명시적으로 캐스팅합니다.
dynamic a = 1;
var r = a + 2; // r의 정적 타입은 dynamic
int i = (int)r; // 필요 시 캐스팅4. 오버로드 선택은 런타임에
오버로드 선택도 런타임 값의 실제 타입에 따라 결정됩니다. 존재하는 가장 적합한 시그니처가 없으면 예외가 납니다.
void M(int n) => Console.WriteLine($"int: {n}");
void M(string s) => Console.WriteLine($"string: {s}");
dynamic v = 10;
M(v); // int 오버로드 선택
v = "10";
M(v); // string 오버로드 선택5. ExpandoObject: 필드/메서드를 즉석에서 추가
ExpandoObject는 동적으로 멤버를 추가/제거할 수 있는 내장 구현입니다. IDictionary로 캐스팅해 키-값 열거도 가능합니다.
using System;
using System.Dynamic;
using System.Collections.Generic;
dynamic person = new ExpandoObject();
person.Name = "Kim";
person.Age = 30;
person.Hello = (Func<string>)(() => $"Hello, {person.Name} ({person.Age})");
Console.WriteLine(person.Hello());
var dict = (IDictionary<string, object>)person;
foreach (var kv in dict)
Console.WriteLine($"{kv.Key} = {kv.Value}");6. DynamicObject: 나만의 동적 객체 만들기
DynamicObject를 상속해 TryGetMember, TrySetMember, TryInvokeMember 등을 오버라이드하면 원하는 규칙으로 동작을 구성할 수 있습니다.
using System;
using System.Dynamic;
using System.Collections.Generic;
public class Bag : DynamicObject
{
private readonly Dictionary<string, object> _map = new();
public override bool TryGetMember(GetMemberBinder binder, out object result)
=> _map.TryGetValue(binder.Name, out result);
public override bool TrySetMember(SetMemberBinder binder, object value)
{
_map[binder.Name] = value;
return true;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
if (binder.Name == "Sum" && args.Length == 2 && args[0] is int a && args[1] is int b)
{
result = a + b;
return true;
}
result = null!;
return false;
}
}
dynamic bag = new Bag();
bag.Title = "My Bag";
bag.Count = 3;
Console.WriteLine($"{bag.Title} ({bag.Count})");
Console.WriteLine(bag.Sum(3, 4)); // 77. COM/Office 자동화에 유용
COM API는 object 타입과 optional 파라미터가 많은데, dynamic을 쓰면 캐스팅/리플렉션 없이 자연스럽게 호출할 수 있습니다.
using System;
using System.IO;
using Excel = Microsoft.Office.Interop.Excel;
var excel = new Excel.Application { Visible = false };
try
{
dynamic wb = excel.Workbooks.Add();
dynamic ws = wb.ActiveSheet;
ws.Cells[1, 1].Value = "Hello";
wb.SaveAs(Path.Combine(Environment.CurrentDirectory, "demo.xlsx"));
wb.Close();
}
finally
{
excel.Quit();
}프로덕션 코드에서는 예외 처리, 릴리즈(마샬 해제), 32/64비트 호환 등을 추가로 고려합니다.
8. JSON을 dynamic으로 빠르게 읽기
Newtonsoft.Json을 쓰면 간단히 dynamic으로 접근할 수 있습니다. 빠른 프로토타이핑에 유용하나, 스키마가 고정된 경우에는 DTO로의 정적 역직렬화를 권장합니다.
using Newtonsoft.Json;
string json = "{\"name\":\"kim\",\"age\":30}";
dynamic obj = JsonConvert.DeserializeObject(json);
Console.WriteLine((string)obj.name); // kim
Console.WriteLine((int)obj.age); // 309. DLR와 성능: 캐시되지만 정적보다 느림
C# 컴파일러는 dynamic 호출을 DLR의 CallSite로 변환해 첫 호출 때 바인딩하고, 이후 같은 런타임 타입 패턴에 대해 규칙을 캐시합니다(다중 디스패치 규칙 캐시). 캐시 덕분에 반복 호출은 빨라지지만, 정적 호출보다 느린 것은 사실입니다. 성능 민감 경로(루프 내부, 할당량 많은 경로)에서는 지양하고, 경계 지점(입출력, 상호운용)에서만 사용을 권장합니다.
또한 일부 프로젝트에서는 런타임 바인더를 위해 Microsoft.CSharp 패키지 참조가 필요할 수 있습니다.
10. 언제 쓰고, 언제 피할까
권장 사용:
- COM 자동화(Office Interop)나 동적 언어(IronPython 등)와의 상호 운용
- 스키마가 자주 변하는 외부 데이터(JSON, 스크립트 객체) 탐색
- 프로토타이핑, 실험적인 DSL 작성(DynamicObject)
지양 상황:
- 핵심 도메인 로직(정적 타입 안정성, 리팩터링 지원이 중요)
- 고성능 경로(정적 바인딩 또는 소스 생성 기반 매핑 권장)
- 장기 유지보수 코드(컴파일 타임 피드백 부재로 회귀 위험 증가)
요약: dynamic은 C#의 정적 타입 시스템을 보완하는 실용 도구입니다. 경계에서 유연성을 얻되, 내부 로직은 정적으로 유지하는 것이 안전합니다.
'C#' 카테고리의 다른 글
| C# CancellationToken으로 작업 취소 구현하기 (0) | 2026.04.18 |
|---|---|
| C# Lock과 Monitor로 스레드 동기화 (0) | 2026.04.17 |
| C# 인터롭(Interop)으로 네이티브 코드 호출하기 (0) | 2026.04.16 |
| C# 애트리뷰트 (Attribute) 정의와 활용 (1) | 2026.04.16 |
| C# 접근 제한자 (Access Modifiers) (0) | 2026.04.15 |