연산자 오버로딩
- 오버로딩과 오버라이딩의 공통점 :
- 오버로딩 : 매개변수가 다른 같은이름의 메서드를 정의하는것이다.
- 오버 라이딩 : 부모클래스의 가상 함수를 자식 클래스에서 재정의하는 것이다.
개념 이해
- 연산자 오버로딩(Operator Overloading)은 사용자 정의 형식에 대해 연산자의 동작을 정의하는 기능임
- 주로 기본 데이터 형식과 유사한 사용자 정의 형식에 사용함
- 예) 복소수, 분수, 행렬 등의 수학적 형식
오버로드 가능한 연산자
오버로드할 수 없는 연산자
연산자 설명
| = |
할당 연산자 |
| . |
멤버 접근 연산자 |
| ?: |
조건부 연산자 |
| ?? |
null 병합 연산자 |
| ?. |
null 조건부 연산자 |
| -> |
포인터 멤버 접근 연산자 |
| [] |
인덱서 (대신 인덱서 프로퍼티로 구현) |
| () |
메서드 호출 |
특수 연산자
- 형식 변환 연산자: implicit, explicit
- 진리값 연산자: true, false
자동 오버로드되는 연산자
- 복합 대입 연산자(+=, -= 등)는 기본 연산자(+, - 등) 오버로드 시 자동으로 오버로드됨
- 조건부 AND/OR 연산자(&&, ||)는 비트 AND/OR 연산자(&, |) 오버로드 시 자동으로 오버로드됨
- // 사용 예시 var x = new LogicalValue(true); var y = new LogicalValue(false); // & 연산자 사용 var result1 = x & y; // &&도 자동으로 사용 가능 var result2 = x && y; // & 연산자를 기반으로 동작함 public struct LogicalValue { bool value; public LogicalValue(bool value) { this.value = value; } // & 연산자 오버로드 public static LogicalValue operator &(LogicalValue a, LogicalValue b) { return new LogicalValue(a.value & b.value); } // | 연산자 오버로드 public static LogicalValue operator |(LogicalValue a, LogicalValue b) { return new LogicalValue(a.value | b.value); } }
- 주의: && 와 || 는 단락 평가(short-circuit evaluation)를 수행함
- && : 첫 번째 피연산자가 false면 두 번째 피연산자를 평가하지 않음
- || : 첫 번째 피연산자가 true면 두 번째 피연산자를 평가하지 않음
연산자 함수 정의 규칙
- operator 키워드를 사용하여 정의함
- static과 public 한정자가 필요함
- 매개변수는 연산자의 피연산자를 나타냄
- 반환 형식은 연산의 결과 형식임
- 최소한 하나의 매개변수는 해당 형식이어야 함
예제 코드
기본 연산자 오버로딩
public struct Note
{
int value;
public Note(int semitonesFromA) { value = semitonesFromA; }
public static Note operator +(Note x, int semitones)
{
return new Note(x.value + semitones);
}
}
// 사용 예시
Note B = new Note(2);
Note CSharp = B + 2;
암시적/명시적 변환 연산자
public struct Note
{
// 암시적 변환 - 주파수(Hz)로 변환
public static implicit operator double(Note x)
=> 440 * Math.Pow(2, (double)x.value / 12);
// 명시적 변환 - 주파수에서 Note로 변환
public static explicit operator Note(double x)
=> new Note((int)(0.5 + 12 * (Math.Log(x/440) / Math.Log(2))));
}
// 사용 예시
Note n = (Note)554.37; // 명시적 변환
double x = n; // 암시적 변환
- 메서드를 이용한 형변환 시도시 작성해야할 키워드
- implicit : 암시적 형변환 키워드
- explicit : 명시적 형변환 키워드
- 데이터 손실이 일어날 경우 명시적 형변환을 사용하는게 좋다.
true/false 연산자 오버로딩
// 사용 예시
SqlBoolean a = SqlBoolean.Null;
if (a)
Console.WriteLine("True");
else if (!a)
Console.WriteLine("False");
else
Console.WriteLine("Null");
public struct SqlBoolean
{
public static bool operator true(SqlBoolean x)
=> x.m_value == True.m_value;
public static bool operator false(SqlBoolean x)
=> x.m_value == False.m_value;
public static SqlBoolean operator !(SqlBoolean x)
{
if (x.m_value == Null.m_value) return Null;
if (x.m_value == False.m_value) return True;
return False;
}
public static readonly SqlBoolean Null = new SqlBoolean(0);
public static readonly SqlBoolean False = new SqlBoolean(1);
public static readonly SqlBoolean True = new SqlBoolean(2);
private SqlBoolean(byte value) { m_value = value; }
private byte m_value;
}
주의사항
- 연산자 오버로딩은 해당 형식의 자연스러운 의미를 반영해야 함
- 직관적이지 않은 연산자 오버로딩은 피해야 함
- 형변환의 경우:
- implicit: 데이터 손실이 없고 항상 성공하는 경우에만 사용
- explicit: 데이터 손실 가능성이 있거나 실패할 수 있는 경우 사용
전처리기
전처리기 지시문의 개념
- 전처리기 지시문은 컴파일러에게 코드 영역에 대한 추가 정보를 제공하는 구문임
- 모든 전처리기 지시문은 #으로 시작함
- 컴파일 타임에 일어난다.
기본 지시문
심볼 정의와 해제
- #define : 심볼을 정의함
- #undef : 심볼 정의를 제거함
#define DEBUG // DEBUG 심볼 정의
#undef DEBUG // DEBUG 심볼 해제
조건부 컴파일
- 조건부 지시문을 사용해 코드의 특정 영역을 컴파일에 포함하거나 제외할 수 있음
- #if, #elif, #else, #endif 사용
#define DEBUG
class MyClass
{
int x;
void Foo()
{
#if DEBUG
Console.WriteLine("테스트 중: x = {0}", x);
#endif
}
}
- 프로젝트 수준에서 .csproj 파일에 정의할 수도 있음
<PropertyGroup>
<DefineConstants>DEBUG;ANOTHERSYMBOL</DefineConstants>
</PropertyGroup>
조건부 연산자 사용
- #if와 #elif 지시문에서 ||, &&, ! 연산자 사용 가능
#if TESTMODE && !DEBUG
// 이 코드는 TESTMODE가 정의되고 DEBUG가 정의되지 않은 경우에만 컴파일됨
#endif
경고와 오류
- #warning : 컴파일러 경고를 생성함
- #error : 컴파일러 오류를 생성함
#if BETA
#warning "베타 버전에서는 이 기능이 불완전할 수 있음"
#endif
#if !UNDERTESTED
#error "이 코드는 충분한 테스트가 필요함"
#endif
코드 구역화
- #region과 #endregion : 코드를 논리적 그룹으로 구성함
- IDE에서 코드 접기(folding)를 지원하는데 사용됨
class Game
{
#region 필드선언
private int score;
private string playerName;
private bool isGameOver;
#endregion
#region 게임로직
public void UpdateScore(int points)
{
score += points;
CheckGameOver();
}
private void CheckGameOver()
{
if (score > 1000)
isGameOver = true;
}
#endregion
}
컴파일러 경고 제어
- #pragma warning : 특정 경고를 선택적으로 억제할 수 있음
public class Foo
{
static void Main() { }
#pragma warning disable 414
static string Message = "Hello"; // 414 경고 비활성화
#pragma warning restore 414 // 414 경고 다시 활성화
}
Conditional 어트리뷰트
- Conditional 어트리뷰트가 적용된 어트리뷰트는 지정된 전처리기 심볼이 있는 경우에만 컴파일됨
- 어트리뷰트(Attribute)는 코드에 메타데이터를 추가하는 선언적인 태그임
// file1.cs - 어트리뷰트 정의
#define DEBUG
using System;
using System.Diagnostics;
[Conditional("DEBUG")]
public class TestAttribute : Attribute {}
// file2.cs - 어트리뷰트 사용
[Test] // TestAttribute의 축약형
class Foo
{
[Test] // TestAttribute의 축약형
string s;
}
- DEBUG 심볼이 정의된 경우:
- [Test] 어트리뷰트들이 컴파일된 코드에 포함됨
- DEBUG 심볼이 정의되지 않은 경우:
- [Test] 어트리뷰트들이 무시되고 컴파일된 코드에서 제외됨
- 활용:
- 테스트 관련 어트리뷰트를 개발 빌드에만 포함하고 싶을 때
- 디버깅용 어트리뷰트를 디버그 빌드에만 포함하고 싶을 때
'공부 > C#' 카테고리의 다른 글
| Enumeration, IEnumerator, 이터레이터 사용, ICollection, IList, 읽기 전용 인터페이스, 배열 클래스 (8) |
2025.08.26 |
| .Net 라이브러리 (7) |
2025.08.26 |
| 익명 타입, 튜플, 레코드, 패턴, 어트리뷰트 (4) |
2025.08.25 |
| Nullable, 확장 메서드, 메서드 체이닝 (0) |
2025.08.19 |
| 익명 메서드, 예외처리, 열거자, 반복자, 시퀀스 조합 (2) |
2025.08.18 |