using으로 네임스페이스를 분명히 가져왔는데도, 컴파일러가 그 안에 있는 클래스를 찾지 못하는 경우가 있습니다.
클래스의 메서드를 호출하면, 그런 이름이 없다는 CS0234 오류가 납니다.
네임스페이스와 클래스의 이름이 같을 때 생기는 문제입니다.
using Logger;
Logger.Write("시작");
// error CS0234: 'Logger' 네임스페이스에 'Write' 형식 또는
// 네임스페이스 이름이 없습니다.
Write는 Logger 클래스에 정의된 public static 메서드입니다. using도 제대로 붙였습니다.
그런데 오류 메시지는 "메서드가 없다"가 아니라 "형식 또는 네임스페이스 이름이 없다"입니다. 이 문구에 원인이 그대로 드러나 있습니다.
네임스페이스와 클래스가 같은 이름입니다
라이브러리 코드가 이렇게 되어 있다고 해 보겠습니다.
namespace Logger // 네임스페이스
{
public static class Logger // 클래스
{
public static void Write(string message) { ... }
}
}
라이브러리 이름이 Logger이고 로그를 남기는 기능을 제공하니, 클래스 이름도 자연스럽게 Logger로 짓게 됩니다.
이 코드는 라이브러리 안에서는 문제없이 빌드됩니다.
컴파일러는 네임스페이스를 먼저 찾습니다
C# 컴파일러는 Logger라는 이름을 만나면, 먼저 같은 이름의 네임스페이스가 있는지 확인합니다. 네임스페이스가 있으면 Logger를 네임스페이스로 보고, using으로 가져온 Logger 클래스는 확인하지 않습니다. 클래스는 같은 이름의 네임스페이스가 없을 때만 확인합니다.
그래서 컴파일러는 Logger.Write를 이렇게 해석합니다.
Logger: 전역에 같은 이름의 네임스페이스가 있으므로 네임스페이스로 결정합니다.- 그 네임스페이스 안에서
Write라는 타입을 찾습니다. - 그런 타입은 없으므로 CS0234 오류가 납니다.
1번에서 이름이 네임스페이스로 결정되었기 때문에, using으로 가져온 Logger 클래스는 아예 후보로 검토되지 않습니다. 컴파일러는 메서드를 찾는 단계까지 가지도 않습니다.
세 가지 경우를 빌드해 봤습니다
| 사용하는 쪽 코드 | 결과 |
|---|---|
전역 스코프 + using Logger; |
CS0234 |
namespace MyApp + using Logger; |
CS0234 |
namespace Logger.Sample |
통과 |
세 번째 경우만 빌드에 성공합니다. 코드가 Logger.Sample 네임스페이스 안에 있으면, 컴파일러는 이 코드가 속한 Logger 네임스페이스 안에서 먼저 이름을 찾습니다. 그 안에 Logger 클래스가 있으므로 Logger를 클래스로 보고, Write 메서드도 제대로 찾습니다. 멀리 있는 전역의 Logger 네임스페이스보다 가까이 있는 Logger 클래스를 먼저 찾는 셈입니다.
라이브러리 테스트로는 이 문제를 찾지 못합니다
라이브러리의 테스트 프로젝트는 보통 Logger.Tests 같은 네임스페이스를 사용합니다. 앞의 세 번째 경우에 해당하므로 테스트는 모두 통과합니다.
반면 라이브러리를 가져다 쓰는 쪽은 자기 네임스페이스를 Logger 아래에 둘 이유가 없습니다. 그래서 대부분 두 번째 경우에 해당합니다. 테스트는 모두 통과하는데, 정작 사용하는 쪽에서는 Logger.Write("시작"); 한 줄조차 컴파일되지 않습니다.
Logger.Logger.Write(...)처럼 전체 이름을 쓰면 컴파일은 됩니다. 하지만 라이브러리를 쓰는 사람마다 이 방법을 직접 찾아내야 하므로 근본적인 해결책은 아닙니다.
해결 방법: 클래스 이름을 네임스페이스와 다르게 짓습니다
namespace Logger
{
public static class Log
{
public static void Write(string message) { ... }
}
}
using Logger;
Log.Write("시작"); // 통과
이제 Log라는 이름의 네임스페이스는 없으므로, 컴파일러는 using으로 가져온 Log 클래스를 바로 찾습니다.
이미 배포한 라이브러리라면 기존 Logger 클래스를 바로 지우지 않는 편이 좋습니다. [Obsolete] 특성을 붙이고, 호출을 Log에 넘기는 래퍼로 남겨 둡니다. 그러면 전체 이름으로 호출하던 기존 코드는 경고만 표시되고 계속 컴파일됩니다.
유명한 패키지도 이렇게 이름을 짓습니다
| 네임스페이스 | 진입점 클래스 |
|---|---|
Serilog |
Log |
AutoMapper |
Mapper |
Polly |
Policy |
Newtonsoft.Json |
JsonConvert |
네임스페이스에는 제품 이름을, 클래스에는 역할 이름을 붙입니다. Log, Mapper, Policy는 모두 흔한 단어입니다. 그래도 네임스페이스가 범위를 좁혀 주기 때문에 실제로 헷갈리는 일은 드뭅니다. 사용하는 쪽 코드에 같은 이름의 타입이 있다면, 그때 별칭을 지정하면 됩니다.
using Log = Logger.Log;
정리
테스트가 모두 통과해도, 라이브러리를 가져다 쓰는 쪽에서도 컴파일된다고 장담할 수는 없습니다. 테스트 프로젝트가 라이브러리와 같은 네임스페이스 아래에 있으면, 사용하는 쪽과는 다른 조건에서 이름을 찾기 때문입니다.
따라서 라이브러리를 만든다면, 사용하는 쪽과 같은 조건에서 컴파일되는지 따로 확인해야 합니다. 별도 프로젝트를 하나 만들고, 그 프로젝트의 네임스페이스에서 라이브러리를 호출하는 코드를 빌드해 보면 이런 문제를 바로 찾을 수 있습니다.
'Programming > C# & WPF' 카테고리의 다른 글
| [WPF] WPF에서 Lottie 사용하기 (0) | 2019.08.02 |
|---|---|
| [C# / WinForm] HWP to IMAGE, 한글문서 이미지 변환 (0) | 2019.07.30 |
| 마샬링(Marshalling) (0) | 2019.06.12 |
| [WPF] Bitmap to Image Converter (0) | 2019.05.26 |