---
title: "연산자도 메서드다"
description: "a + b 의 + 는 결국 메서드 하나입니다. 내가 만든 타입에도 그 메서드를 달 수 있습니다."
author: "유니얼"
pubDate: 2026-09-09
category: "programming/csharp/advanced"
tags: ["C#"]
canonical: https://blog.unialgames.com/posts/csharp-operator-overloading/
---
앞에서 `점` 이라는 값 타입을 만들었습니다. 두 점을 더하려면 지금은 이렇게 씁니다.

```csharp
점 c = new 점(a.X + b.X, a.Y + b.Y);
```

Unity 에서는 이렇게 씁니다.

```csharp
Vector3 c = a + b;
```

`Vector3` 도 그냥 struct 입니다. 우리가 만든 `점` 과 다를 것이 없습니다.
그런데 `+` 가 됩니다. 뭔가 특별해서가 아니라, 누가 그 `+` 를 만들어 뒀기 때문입니다.

> **`a + b` 는 컴파일할 때 `점.op_Addition(a, b)` 라는 메서드 호출이 됩니다. 그 메서드를 우리가 적으면 됩니다.**

## 연산자 오버로딩은 이렇게 생겼다

메서드인데 이름 자리에 `operator` 와 기호가 들어갑니다.

<figure class="diagram"><svg viewBox="0 0 640 150" role="img" aria-label="연산자 오버로딩의 생김새"><text x="20" y="40" class="mono" font-size="14">public static 점 operator +(점 a, 점 b)</text><line x1="60" y1="52" x2="60" y2="76" stroke="currentColor" stroke-opacity="0.25" /><text x="70" y="81" font-size="12.5">반드시 static. 객체가 아니라 타입에 붙습니다</text><line x1="185" y1="52" x2="185" y2="100" stroke="currentColor" stroke-opacity="0.25" /><text x="195" y="105" font-size="12.5">돌려주는 타입. 새 값을 만들어 돌려줍니다</text><line x1="270" y1="52" x2="270" y2="124" stroke="currentColor" stroke-opacity="0.25" /><text x="280" y="129" font-size="12.5">기호 자리. 이항 연산자는 매개변수 둘</text></svg><figcaption>이름이 기호일 뿐 평범한 static 메서드입니다.</figcaption></figure>

```csharp
struct 점
{
    public int X;
    public int Y;

    public 점(int x, int y) { X = x; Y = y; }

    public static 점 operator +(점 a, 점 b)
    {
        return new 점(a.X + b.X, a.Y + b.Y);
    }
}
```

이제 됩니다.

```csharp
점 a = new 점(1, 2);
점 b = new 점(3, 4);
점 c = a + b;              // (4, 6)
```

`static` 이어야 하는 이유가 있습니다. `a + b` 에서 `+` 는 `a` 의 것도 `b` 의 것도 아닙니다.
둘 사이의 일이라 어느 한쪽에 붙이면 이상해집니다. 그래서 타입에 붙입니다.

`a` 나 `b` 를 고치지 않고 **새 값을 만들어 돌려주는 것**도 중요합니다.
`3 + 5` 를 했다고 `3` 이 바뀌지 않는 것과 같습니다.

## == 를 만들면 세 개를 더 만들어야 한다

여기서부터 함정입니다. `==` 를 하나 만들면 컴파일러가 화를 냅니다.

```csharp
public static bool operator ==(점 a, 점 b)
{
    return a.X == b.X && a.Y == b.Y;
}
```

이것만 쓰면 이런 에러가 납니다.

```
'점.operator ==' 를 정의하려면 'operator !=' 도 정의해야 합니다
```

`==` 와 `!=`, `<` 와 `>`, `<=` 와 `>=` 는 반드시 짝으로 만들어야 합니다.
한쪽만 있으면 `a == b` 는 되는데 `a != b` 는 안 되는 이상한 타입이 되기 때문입니다.

그리고 경고가 두 개 더 붙습니다.

```
'점' 이 'operator ==' 를 정의하지만 'Equals' 를 재정의하지 않습니다
'점' 이 'operator ==' 를 정의하지만 'GetHashCode' 를 재정의하지 않습니다
```

이건 에러가 아니라 경고라 그냥 넘어갈 수 있습니다. **넘어가면 나중에 크게 당합니다.**
그 이야기는 이 글 끝에서 다시 합니다.

## 만들 수 있는 것과 없는 것

만들 수 있는 것은 정해져 있습니다.

| 종류 | 기호 |
| --- | --- |
| 이항 | `+` `-` `*` `/` `%` |
| 비교 | `==` `!=` `<` `>` `<=` `>=` |
| 단항 | `+` `-` `!` `~` `++` `--` |
| 비트 | `&` `\|` `^` `<<` `>>` |
| 형변환 | `explicit` `implicit` |

만들 수 없는 것도 알아 둘 만합니다. `=`, `&&`, `||`, `?:`, `.` 은 못 만듭니다.
`a += b` 는 따로 만드는 것이 아니라 `+` 를 만들면 저절로 됩니다. `a = a + b` 로 풀리기 때문입니다.

형변환도 만들 수 있습니다.

```csharp
public static implicit operator 점(int n) => new 점(n, n);
public static explicit operator int(점 p) => p.X;
```

`implicit` 은 캐스팅 없이 되고 `explicit` 은 `(int)p` 처럼 적어야 합니다.
값이 손실되거나 실패할 수 있으면 `explicit` 으로 둡니다. 앞에서 본 캐스팅 규칙과 같은 기준입니다.

## 인덱서는 대괄호를 만드는 것

같은 이야기가 `[]` 에도 있습니다. `list[0]` 이나 `dict["키"]` 의 대괄호도 우리가 만들 수 있습니다.

```csharp
class 격자
{
    private int[] 칸 = new int[9];

    public int this[int x, int y]
    {
        get { return 칸[y * 3 + x]; }
        set { 칸[y * 3 + x] = value; }
    }
}
```

`this[...]` 라고 씁니다. 프로퍼티와 생김새가 같습니다. `get` 과 `set` 이 있고 `value` 로 받습니다.
프로퍼티에 이름 대신 대괄호가 붙었다고 보면 정확합니다.

```csharp
격자 g = new 격자();
g[1, 2] = 7;
Console.WriteLine(g[1, 2]);   // 7
```

2차원 배열처럼 보이는데 안은 1차원 배열입니다.
바깥에서 쓰는 모양과 안의 구조를 따로 정할 수 있다는 것이 인덱서의 쓸모입니다.
행과 열을 하나짜리 번호로 바꾸는 계산은 앞에서 2차원 배열을 볼 때 나왔던 그 식입니다.

## 하면 안 되는 것

기술적으로는 아무 기호에 아무 뜻이나 넣을 수 있습니다. 그래서 규칙이 필요합니다.

**기호가 이미 가진 뜻을 지킵니다.** `+` 는 더하기입니다.
`플레이어 + 아이템` 이 "아이템을 장착한다" 를 뜻하게 만들면 코드를 읽는 사람이 매번 정의를 찾아봐야 합니다.
그건 메서드 이름으로 적으면 됩니다. `플레이어.장착(아이템)` 이 훨씬 낫습니다.

**원래 값을 안 고칩니다.** `a + b` 를 했는데 `a` 가 바뀌면 아무도 예상하지 못합니다.

**비싼 일을 넣지 않습니다.** 연산자는 짧고 싸 보입니다. 그 안에서 파일을 읽거나 네트워크를 부르면
읽는 사람이 비용을 못 봅니다.

그래서 실제로 쓸 만한 자리는 좁습니다. 좌표, 색, 시간, 돈, 분수처럼 **원래 수학에서 더하고 빼던 것**입니다.
`Vector3` 나 `Quaternion` 이 딱 그 자리입니다.

## 정리

- `a + b` 는 static 메서드 호출로 바뀝니다. 그 메서드를 `operator +` 로 적으면 됩니다
- `static` 이어야 하는 이유는 어느 한쪽의 것이 아니기 때문입니다
- `==` 와 `!=` 는 짝으로 만들어야 합니다. 한쪽만은 컴파일 에러입니다
- `this[...]` 로 인덱서를 만들면 바깥에서 대괄호로 쓸 수 있습니다
- 기호가 원래 가진 뜻을 벗어나면 만들지 않는 편이 낫습니다