C# 전처리기 구문 어떻게들 사용하고 계신가요?

사실 어떻게들 쓰고 계신지 보다는 어떻게 리팩토링해서 사용하는 지가 궁금합니다. ?ㅅ?

제가 직접 처음부터 C# 으로 개발하는 프로젝트에서는
딱히 전처리기 구문이 필요한 경우가 거의 없긴 했는데…

이직하고 나서 만났던 코드들 중에
이전 작업자가 온갖 전처리기 상수를 정의해서
#if #elif #else 블럭에 허우적 대 본 경험이 있어서요.

그래도 DEBUG / RELEASE 정도는 나름 쓸모가 있다고 생각은 하는데
이것도 사실 #if 어쩌고 하면서 들여쓰기 깨는 거 자체를 저는 약간… 꼴보기 싫거든요… ~ㅅ~;;

좀 극단적인 표현이지만 예를 살짝만 들어보자면

   var list = new [] { "a", "b", "c" };
   foreach(var chr in list)
   {	
#if DEBUG
   	Console.WriteLine(chr);
   }
#else
   	Console.WriteLine(chr);
   }
#endif	

요딴식으루 작성되어 있는 걸 많이 봐 왔어요. (예를 들어서 저렇다는 겁니다… -ㅅ-;; )

그나마 DEBUG / RELEASE 정도야 그렇다치는데
위 예처럼 중괄호 블럭을 애매하게 포함시킨다든가 하는 경우도 많았고
(그래서 빌드 설정이 바뀌면 당장 빨간불이 죽죽…)

저렇게 코드 진행 중에 들여쓰기를 깨는 듯한 것들만 보면 좀 짜증이 밀려오기도 하구요…;;
(실제로 가독성을 해치기도 하구요…)

그래서 저는 저런 걸 만나면 메서드로 다 분리해서 Conditional 처리해버리는데

[Conditional("DEBUG")]
private void StartByDebug(string chr)
{
	Console.WriteLine(chr);
}

[Conditional("Release")]
private void Start(string chr)
{
	Console.WriteLine(chr);
}

var list = new [] { "a", "b", "c" };
foreach(var chr in list)
{
	StartByDebug(chr);
	
	Start(chr);
}

사실 이것두 깔끔하지 않은 건 마찬가지인 거 같습니다.

게다가 메서드로 분리하면 호출하는 위치에서는 저게 두 번 호출되는 거 같은 느낌이라서요.

다른 전처리기 구문은 잘 모르겠고 (아, 생각도 하기 싫… ;;; )
DEBUG / RELEASE 정도로 나눠서 분기되는 코드를 만든다고 친다면

다들 어떤 식으로 사용하시나요?

깔끔하게 ?ㅅ?

음…
그럼 이거 약간 범위를 좁혀서 #if DEBUG 정도만 얘기해보면 어떨까요?

예시처럼
다들 그냥 코드 중에

#if DEBUG
   	Console.WriteLine(chr);
   }
#else
   	Console.WriteLine(chr);
   }
#endif	

요렇게 쓰시나요?
아니면 이것도 정리해서 다른 방식으로 쓰실까여?ㅅ?

아… 저는 코드 중간에 #if #endif 나오는 걸 좀 피라고 싶셉습니다… ;ㅅ;

  • 참고로 테스트하면서 안 사실인데…

LINQPad 는 DEBUG 로 실행되는 것이었군요… 0ㅁ0!

2개의 좋아요

저는 #region 말고는 안쓰는 것 같아요 :thinking:

5개의 좋아요

실행환경설정이라던가 로그기록 정도가 #if로 분기할 만한 내용인데요. 저도 거의 안써서 의미있는 답을 못하고 눈팅만 하고 있었습니다.

이외에 전처리문을 써야하는 경우가 무엇이 있을까요?

3개의 좋아요

저 같은 경우는 실수로 Debug 구성으로 사원들이 빌드해서 배포하지 않도록 빌드된 어플리케이션이 Release인지 Debug인지 UI에 띄워주려고 사용합니다…

3개의 좋아요

저도 개인적으로는 그러한데

이게 네이티브 개발하시던 분 습관인지는 모르겠는데
솔루션 수준에서 분리되어야하는 것들인데
이걸 한 어셈블리에서 코드로 분리 처리하면서 많이 섞어 놓았더라구요.

예를 들면 전처리기에다가 CEF 뭐 이런 거 정의해놓고

#if _CEF_
   chrome.Navigate(); 
#else
   gmap.Navigate();
#endif

(간단히 쓴겁니다…)

=ㅅ=;;

뭐 이런식으로 쓰더라구요.
(대략 #if #else저 구문 안으로 한 1200 줄 정도 코드를 만들어놨더군요. 중복된 코드들을 덕지덕지…)

개인적으로는
이걸 이렇게 써야할 일인가… +ㅁ+! 하면서 수정하는 데 엄청 애먹었죠.

뭐 이건 극단적인 경험(!) 이긴 한데
#if DEBUG 정도는 쓸 수 있겠다는 생각을 하긴 합니다.
그런데 이것도 #if 코드를 실행 흐름 중에 만나는 게 너무 짜증나거든요.
(그래서 [Conditional()] 로 처리하긴 하는데…)

좀 깔끔한 방법을 찾고 싶어서 여쭤봤어요… ;ㅅ;

2개의 좋아요

음… 혹시

그러면 이런 경우는 어떻게 처리하시는 가요?

예를 들면
같은 분기에서 DEBUG 에서 파일에 로그를 쓰고 RELEASE 에서는 Email 을 보내는 상황에서

#if DEBUG
   Logger.Log(message);
#else
   mailRepo.Send(message);
#endif

이런 식이 될 텐데

그냥 이런 식으로 로그를 쓰는 위치에서 #if 로 처리를 하시는 편인가요?

[Conditional()] 로 처리해도 같은 위치에서 코드 상으로 두번 호출하는 것처럼 작성해야해서
그것도 좀 보기 불편한 거 같거든요.

2개의 좋아요

왠만하면 전처리문은 사용 안하는쪽으로 코딩을 해야죠.
전처리문을 사용하는 이유가 대부분 디버깅시 로그 출력이고, 그외에 조건에 따른 기능 변경인데, 이런건 IDE 디버깅이나 NLOG, CONFIG 파일등으로 대체할 수 있기 때문에 굳이 사용할 이유가 없습니다.
그래도 꼭 전처리문을 사용해야한다면 차라리 분기를 따로 만들어서 관리하는데 더 나을 수 있겠죠.

4개의 좋아요

c++에서도 그런용도의 전처리기 사용은 욕먹기 좋습니다.
혼자 죽을때까지 만들거 아닌 이상은 후임자가 보기 드럽거든요.
제 경우는 특별한 경우나 테스트아니면 디버그 전처리기만 사용 합니다.

위의 특별한 경우도 디버그 때문입니다.
예를들어 윈도우서비스를 만드는데. 매번 디버깅이 번거로우니
시작 부분하고 몇군데만 전처리기로 나눠서 WIndowsForms로 실행되게 한적이 있습니다.
이것도 디버그 전처리기로 가능은 하지만 그때 사용한 API들이 윈도우서비스랑 윈폼에서 아예 따로 노는것들이
있어서 빌드 설정에 Debug_WinForm, Release_WinForm 을 만들었죠.
현재는 서비스는 와치독 형태로 실행되는 프로그램의 감시 및 문제 발생 시 재실행만 하도록 수정했습니다.

4개의 좋아요

오래된 글이 었군요.

저 같은 경우 SDK 작업을 할 때 멀티타기팅 이슈가 있을 때 사용합니다.

예를 들어 라이브러리가 .NET Standard 2.0 과 .NET 6를 지원해야하는 경우인데,

프로퍼티 사용 시 { get; init; } 을 저는 유용하게 사용하는 편인데 C# 9.0 문법이라서 .NET 6부터 가능합니다.

그래서

#if NET6_0_OR_GREATER
public string ABC { get; init; }
#else
public string ABC { get; set; }
#endif

이렇게 쓰고 있습니다.

솔직히 .NET Standard 2.0으로 그냥 만들어버리면 이렇게 안해도 되겠지만 init 문법을 꽤 잘 쓰고 있어서인데,

다들 아시는 내용이겠지만, init 문법은 interface로 적용이 가능하지만, private set 문법은 interface로 사용할 수 가 없습니다.

그래서 저는 위와 같은 경우에 사용합니다.

4개의 좋아요

저도 거의 사용안하지만 다음과 같은 용도로는 가끔 사용합니다.

  1. try catch문에서 디버그 모드일 경우 catch 문 내에서 오류 처리를 하지 않고 다시 thow하는 용도 또는 릴리즈 모드에서만 try catch가 적용되도록 해야 하는 경우
    개발 초기 단계에서 실제 어디에서 에러가 났는지 Visual Studio에서 딱 화면에 찍어주니까 편리한 점이 있습니다.

특히, 윈도우 프로그램 개발시에는 어떤 상황에서라도 일단은 프로그램이 휙 죽어버리는 것은 제품 이미지에 매우 좋지 않기 때문에 방어적 오류 처리를 많이 하는 편인데, 그러다보면 예상하지 못한 문제 발생 시 원인 찾기가 좀 어려울 수 있는데 그럴 때 도움이 됩니다. 개발이 좀 더 진행되면 빼버리기도 하는데 가장 바깥쪽 try catch에는 일부 남겨놓기도 합니다.

  1. 다양한 플랫폼 프로젝트에서 소스 레벨로 공유할 경우
    이게, 리얼 월드에서는 Visual Studio 2008과 같이 구버전 개발툴을 사용하거나 .NET 4.7.2 미만인 프로젝트들이 있어서 (.NET Standard 2.0 제대로 사용하려면 최소 버전은 4.6.1부터이지만 실제로는 4.7.2는 되어야 불필요한 어셈블리 참조가 많이 줄어듭니다.) 소스 파일 레벨로 코드 공유를 해야 하는 경우가 간혹 있습니다.

이런 경우에는 어쩔 수 없이 전처리기를 사용해야죠.

이외에도 잘 사용하기만 하면 유용한 도구인데 예시처럼 console.writeline을 찍는 수준으로 사용하는데다가 중괄호까지 헷갈리게 하는 경우가 있는지는 처음 봤네요. 새로운 세상을 본 것 같습니다~

4개의 좋아요

저도 디버그/릴리즈에 따라 분기를 정하거나, 회사에서 쓸 라이브러리를 만들 때 기본은 닷넷 코어지만 혹시나 닷넷 프레임워크 환경에서 사용할 일이 있을까봐 @Vincent 님처럼 버전을 구별하는 용도로 사용합니다.

그 외에는 개발 과정이나 리팩토링 단계에서 교체나 선택이 필요한데 그게 광범위하게 영향을 미칠 때 코드를 테스트하기 위해서도 사용합니다. 가령 변수 타입을 바꾼다거나 특정 로직을 변경해서 실행해 보고 싶은데 다수의 코드를 수정해야 하고, 기존 코드와 비교해 보거나 원래대로 되돌려야 할 가능성이 있는 상황이라면 프로젝트 파일에 상수 하나 정의해 놓고 필요에 따라 상수를 정의한 부분만 껐다 켜면서 테스트해 보는 게 편하더라구요. 로직을 바꾸면서도 잘될 거란 확신이 없다 보니 더더욱 그렇습니다

물론 테스트를 끝내고 나서는 조건부로 분리했던 부분을 다시 지워야 하니 결과적으로 일을 더하는 것이긴 하지만요.

2개의 좋아요