C# 마법같은 문법 (Magical Syntax)
개요
C# 언어에는 특정 API가 아닌 규칙(convention)에 기반한 마법같은 문법들이 존재합니다. 이러한 문법들은 잘 정의된 이름을 가진 메서드들을 기반으로 하지만, 기본 클래스나 인터페이스에 정의되어 있지 않음에도 불구하고 정상적으로 작동합니다.
1. 컬렉션 열거 (Enumerating Collections)
핵심 개념
foreach
문은 실제로 GetEnumerator
메서드 호출을 감싸는 구문입니다.
중요한 특징
IEnumerable
또는IEnumerable<T>
구현이 필수가 아님GetEnumerator
메서드만 있으면foreach
사용 가능- 반환 타입은
IEnumerator
또는IEnumerator<T>
이어야 함
코드 예제
public class Enumerable
{
public IEnumerator GetEnumerator()
{
yield return 1;
yield return 2;
yield return 3;
}
}
var e = new Enumerable();
foreach (int i in e) { /* 1, 2, 3 */ }
주의사항
- 클래스가
IEnumerable
을 구현하지 않아도foreach
사용 가능 - 컴파일러가
GetEnumerator
메서드의 존재만 확인
2. 튜플 분해 (Deconstruction to Tuples)
핵심 개념
C# 7에서 도입된 튜플을 사용하여 메서드에서 여러 값을 반환할 수 있으며, 클래스를 튜플로 분해할 수 있습니다.
중요한 특징
Deconstruct
메서드를 통해 클래스를 튜플로 분해 가능- 매개변수는 반드시
out
키워드 사용 - 여러 개의
Deconstruct
메서드 오버로드 가능
기본 예제
public class Rectangle
{
public int Height { get; set; }
public int Width { get; set; }
public void Deconstruct(out int h, out int w)
{
h = this.Height;
w = this.Width;
}
}
var rectangle = new Rectangle { Height = 10, Width = 20 };
var (h, w) = rectangle;
고급 분해 예제
public void Deconstruct(out int perimeter, out int area, out bool square)
{
perimeter = this.Width * 2 + this.Height * 2;
area = this.Width * this.Height;
square = this.Width == this.Height;
}
var (perimeter, area, square) = rectangle;
주의사항
- 튜플 선언과 일치하는
Deconstruct
메서드를 찾지 못하면 예외 발생 - 모든 매개변수는
out
키워드 필수
3. 컬렉션 초기화 (Collection Initialization)
핵심 개념
C# 6부터 컬렉션을 간결한 문법으로 초기화할 수 있습니다.
중요한 특징
Add
메서드의 존재가 핵심IEnumerable
또는IEnumerable<T>
구현 필수- 중괄호 안의 각 항목에 대해
Add
메서드가 여러 번 호출됨
기본 예제
var strings = new List<string> { "A", "B", "C" };
사용자 정의 컬렉션 예제
public class Collection : IEnumerable
{
public IEnumerator GetEnumerator() => /* ... */
public void Add(string s) { /* ... */ }
}
var col = new Collection { "A", "B", "C" };
딕셔너리 초기화
방법 1: 중괄호 구문
var dict = new Dictionary<string, int> { { "A", 1 }, { "B", 2 }, { "C", 3 } };
방법 2: 인덱서 구문
var dict = new Dictionary<string, int>
{
["A"] = 1,
["B"] = 2,
["C"] = 3
};
다중 오버로드 지원
public void Add(int i) { /* ... */ }
public void Add(string s) { /* ... */ }
var col = new Collection { 1, 2, 3, "a", "b", "c" };
주의사항
Add
메서드가 없으면 컬렉션 초기화 구문 사용 불가IEnumerable
구현은 필수이지만Add
메서드는 정의하지 않음
4. 소멸자 (Finalizers)
핵심 개념
.NET의 모든 클래스는 Object
에서 상속받으므로 Finalize
메서드를 가집니다.
중요한 특징
~ClassName
문법 사용- 클래스에서만 사용 가능 (구조체 불가)
- 기본
Finalize
메서드는 자동으로 호출됨
코드 예제
public class MyClass
{
~MyClass()
{
//do cleanup
}
}
주의사항
- 기본
Finalize
메서드를 명시적으로 호출할 필요 없음 - 리소스 정리용으로만 사용하는 것이 권장됨
5. 연산자 오버로딩 (Operator Overloading)
핵심 개념
C#에서는 연산자를 오버로드하고 캐스트 연산자를 정의할 수 있습니다.
캐스트 연산자
명시적 캐스트
public class MyClass
{
private bool _someInternalField;
public static explicit operator bool(MyClass mc)
{
return mc._someInternalField;
}
}
MyClass c = new MyClass();
bool b = (bool) c; // 명시적 캐스트 필요
암시적 캐스트
MyClass c = new MyClass();
bool b = c; // 암시적 캐스트 (implicit 키워드 사용 시)
오버로드 가능한 연산자
연산자 그룹 | 연산자 | 특별 규칙 |
---|---|---|
단항 연산자 | +x, -x, !x, ~x, ++, --, true, false |
true 와 false 는 함께 오버로드 필요 |
이항 연산자 | `x + y, x - y, x * y, x / y, x % y, x & y, x | y, x ^ y, x << y, x >> y, x >>> y` |
비교 연산자 | x == y, x != y, x < y, x > y, x <= y, x >= y |
쌍으로 오버로드 필요: == 와 != , < 와 > , <= 와 >= |
덧셈 연산자 오버로드 예제
public class Complex(int Real, int Imaginary)
{
public static Complex operator + (Complex c1, Complex c2) =>
new(c1.Real + c2.Real, c1.Imaginary + c2.Imaginary);
}
Complex c1 = new(10, 0);
Complex c2 = new(5, 5);
Complex sum = c1 + c2;
주의사항
- 연산자의 원래 의미를 존중해야 함
- 예상치 못한 동작을 방지하기 위해 신중하게 사용
- 관련 연산자들은 쌍으로 오버로드해야 함
6. 사용자 정의 Await (Custom Await)
핵심 개념
Task
객체가 아닌 사용자 정의 클래스에서도 await
사용이 가능합니다.
필요한 조건
GetAwaiter
메서드 제공INotifyCompletion
인터페이스 구현IsCompleted
불린 속성 제공GetResult
메서드 제공 (매개변수 없음)
void 반환 타입 예제
public class CustomVoidAwaitable
{
public CustomVoidAwaiter GetAwaiter() => throw new NotImplementedException();
}
public class CustomVoidAwaiter : INotifyCompletion
{
public void OnCompleted(Action continuation) => throw new NotImplementedException();
public bool IsCompleted => throw new NotImplementedException();
public void GetResult() => throw new NotImplementedException();
}
제네릭 반환 타입 예제
public class CustomAwaitable<T>
{
public CustomAwaiter<T> GetAwaiter() => throw new NotImplementedException();
}
public class CustomAwaiter<T> : INotifyCompletion
{
public void OnCompleted(Action continuation) => throw new NotImplementedException();
public bool IsCompleted => throw new NotImplementedException();
public T GetResult() => throw new NotImplementedException();
}
사용 방법
CustomVoidAwaitable ca = ...;
await ca;
CustomAwaitable<string> csa = ...;
var result = await csa;
주의사항
ValueTask<T>
도 이 패턴으로 구현됨- 모든 필수 구성 요소를 정확히 구현해야 함
7. 사용자 정의 쿼리 패턴 (Custom Query Pattern)
핵심 개념
C# 쿼리 구문을 사용자 정의 클래스에서 사용할 수 있습니다.
필요한 표준 연산자들
Cast
GroupBy
GroupJoin
Join
Order
OrderBy
OrderByDescending
Select
SelectMany
ThenBy
ThenByDescending
Where
기본 구조 예제
class CustomQueryable
{
public CustomQueryable<T> Cast<T>() => throw new NotImplementedException();
}
class CustomQueryable<T> : CustomQueryable
{
public CustomQueryable<T> Where(Func<T,bool> predicate) => throw new NotImplementedException();
public CustomQueryable<U> Select<U>(Func<T,U> selector) => throw new NotImplementedException();
public CustomQueryable<V> SelectMany<U,V>(Func<T,CustomQueryable<U>> selector, Func<T,U,V> resultSelector) => throw new NotImplementedException();
// ... 기타 연산자들
}
정렬 가능한 쿼리 클래스
class CustomOrderedQueryable<T> : CustomQueryable<T>
{
public CustomOrderedQueryable<T> ThenBy<K>(Func<T,K> keySelector) => throw new NotImplementedException();
public CustomOrderedQueryable<T> ThenByDescending<K>(Func<T,K> keySelector) => throw new NotImplementedException();
}
그룹화 클래스
class CustomGroup<K,T> : CustomQueryable<T>
{
public K Key { get; } => throw new NotImplementedException();
}
쿼리 사용 예제
var query = from c in myObj.Get<string>()
where c.Length > 0
orderby c
select c;
주의사항
- .NET 9/C# 13에서 추가 연산자들이 도입됨
- 모든 필요한 연산자 메서드를 구현해야 함
- 쿼리 구문의 각 절은 해당하는 메서드로 변환됨
참고 자료
본 문서에서 언급된 추가 정보를 위한 링크들:
실용적인 팁
개발 시 고려사항
- 규칙 기반 문법의 이해: C#의 많은 기능들이 특정 메서드 이름에 의존한다는 점을 인식
- 신중한 연산자 오버로딩: 예상치 못한 동작을 방지하기 위해 연산자의 원래 의미 존중
- 성능 고려: 사용자 정의 구현 시 성능 영향 검토
- 명명 규칙 준수:
GetEnumerator
,Deconstruct
,Add
등의 정확한 메서드 이름 사용
주의사항
- 기괴한 코드 방지: 마법같은 문법의 남용으로 인한 가독성 저하 주의
- 최신 언어 변화 추적: C# 언어의 지속적인 변화에 대한 업데이트 필요
- 문서화의 중요성: 사용자 정의 구현 시 충분한 문서화 제공