본문 바로가기

💻 내 소개 안녕하세요 엄청짱 프로그래머 손다빈 입니다.
  • 나이 : 96년생
  • 특이사항 : MZ세대, INFJ, 오른손잡이, 아이폰 유저
  • 좋아하는 음식 : 햄버거피자치킨솥뚜껑삼겹살떡볶이오튀김밥
  • 취미 : 개발, Programming, 코딩, 프로그래밍, Coding

🥷기술
Unity
Godot
Cpp
Javascript
D3
Vue

🐱 우리집 고양이 소개
츄르 먹은 후 츄르 먹기 전
  • 이름 : 콜라
  • 나이 : 8살
  • 종 : Nado moreum

📱 개인 프로젝트
🏢 참여한 프로젝트
빌런즈 Life is Pair 도씨어부키우기 직장상사혼내주기 서바이벌빙고 SlitherCoin

🌱 내 잔디밭

Effective C# 요약 본문

글 묶음/내 밥줄 Unity, C#

Effective C# 요약

초긍정 개발자 다빈맨 2022. 11. 20. 14:22

1. 지역변수를 선언할 때는 var 를 사용하는 것이 낫습니다.

더보기
  • 변수의 이름을 지을 때 조금 더 의미가 강조되기 때문에 가독성이 좋아집니다.
    • 참고로 Effective C++ 에서 auto 키워드 사용에 대한 이유와 일치합니다.
  • 구문의 반환 타입을 정확히 알지 못할 때 명시적으로 타입을 지정하는 경우 예상치 못하게 강제 형 변환이 일어날 수 있습니다.

2. const보다 readonly가 좋습니다.

더보기
  • const 는 숫자, enum, 문자열, null 과 같은 내장 타입에 대해서만 사용될 수 있지만 readonly는 어떤 타입이든 사용할 수 있습니다.
  • readonly의 위와 같은 유연성은 컴파일 타임 상수인 const 의 성능적인 장점보다 훨씬 두드러집니다.

 

하지만 컴파일할 때 참조되는 상숫값을 정의 하는 목적일 때는 불가피하게 반드시 const를 사용해야 합니다. (예를들면 switch/case 문의 레이블이나 enum 정의할 때 사용하는 상수)

3. 상속 관계에서 명시적 형변환 보다는 is, as가 좋습니다.

더보기
  • as 나 is 는 런타임에 예외적인 상황이 아니라면 대부분 타입을 검증하는 것을 제외하고는 어떠한 작업도 하지 않기 때문에 캐스트보다 빠릅니다.

4. string.Format() 대신 보간 문자열($)을 사용하세요.

더보기
  • '보간 문자열' 이란 $ 로 시작하는 리터럴 문자열. ex) $"저는 {name} 입니다."

5. 국가별로 다른 문자열을 생성하려면 FormattableString가 좋습니다.

더보기
  • https://learn.microsoft.com/ko-kr/dotnet/api/system.formattablestring?view=net-7.0

6. nameof() 연산자를 적극 활용합시다.

더보기
  • 메소드나 클래스, 프로퍼티 등의 이름을 리터럴 문자열로 적기보다는 nameof() 연산자를 사용하면, IDE 에서 이름 변경과 같은 작업을 진행할때 참조하고 있는 코드들을 전부 일괄 변경할 수 있습니다.
  • 유니티에서 StartCoroutine 과 같은 메소드의 매개변수로 nameof() 를 사용하는 경우 코드 에디터에서 참조를 표기해주기 때문에 편리합니다.

7. 델리게이트(delegate)를 이용하여 콜백을 표현하세요.

더보기
  • 애초에 델리게이트 말고 콜백을 표현할 방법이라고 해봐야 딱히 생각나는게 없습니다.

8. 이벤트 호출 시에는 null 조건 연산자를 사용하세요.

더보기
  • 이벤트 호출시 SomeEvent?.Invoke(); 이렇게 호출하면 간결하고, 멀티 스레드에서도 안전합니다.

9. 박싱과 언박을 최소화 하세요.

더보기
  • 박싱과 언박싱에 대해 이해하세요.
  • 컬렉션은 (.NET 2.0 이상) 제네릭 컬렉션을 사용하세요

10. 베이스 클래스 업그레이드된 경우에만 new 한정자를 사용하세요.

더보기
  • new 한정자를 활용해도 좋은 경우는 베이스 클래스에서 이미 사용되고 있는 메소드를 재정의하여 '완전히' 새로운 베이스 클래스를 만들어야 하는 상황 정도입니다. 
  • 어지간하면 new 한정자를 사용하지 않는 것이 좋습니다. 프로그래머가 호출부에서 동작을 예상하기 어려워지기 때문입니다.

11. .NET 리소스 관리에 대한 이해

더보기
  • 가비지 수집기가 효율적인 알고리즘(Mark/Compact)으로 더이상 사용되지 않는 객체를 자동으로 정리합니다.
  • 하지만, 비관리 리소스 (데이터 베이스 연결, 시스템, COM 객체) 등은 개발자가 직접 관리해야 합니다.
    • 비관리 리소스 생명주기를 관리할 때 finalizerIDisposable 인터페이스 두 가지 메커니즘을 사용할 수 있지만 IDisposable 인터페이스를 통해 구현하는것이 좋습니다.
    • finalizer 는 가비지 수집기의 알고리즘에 의존하여 호출되기 때문에 어느 시점에 호출될지 아무도 알 수 없기 때문입니다. 심지어, 가비지 수집기로 부터 정리되는 시기를 늦추기 때문에 성능에도 영향을 끼칩니다.

12. 생성자 할당 구문보다 생성과 동시에 초기화 하는 형태의 멤버 초기화 구문이 좋습니다

더보기
  • 생성자에서 할당 구문으로 값을 초기화 하지 말고, 멤버 초기화 구문을 이용해서 선언과 동시에 초기화를 정의하세요. 자쳇 초기화 코드를 누락할 수 있기 때문입니다.
  • 멤버 초기화 구문을 이용할 때 다음 경우를 주의하세요.
    • 기본 값이 0이나 null 인 경우 이미 메모리 블럭을 0으로 설정하니 초기화 구문을 넣을 필요가 없습니다. 굳이 추가적인 코드를 생성해서 컴파일러에게 괜한 일을 시킬 필요가 없죠.
    • 동일 객체를 반복해서 초기화 하지 마세요. 예를들어 선언과 동시에 객체를 초기화 하고, 생성자에서 또 다시 초기화 구문을 넣는 상황을 피하십시오.
    • 초기화 과정에서 예외를 처리해야하는 경우, try로 감쌀 수 없기 때문에 이 경우에는 생성자 내부에서 예외 처리 코드를 적절히 구현하면 됩니다.

13. 정적 클래스 멤버를 올바르게 초기화하세요.

더보기

static 변수를 초기화할 때 간단한 초기화라면 멤버 초기화 구문도 괜찮지만 복잡한 초기화 구문이라면 static 생성자를 사용해보는 것도 좋습니다. 싱글톤(Singleton) 패턴을 구현하는 예입니다.

public class MySingleton
{
    private static readonly MySingleton theOneAndOnly;
    
    // static 생성자는 private 혹은 다른 관용구를 사용해서는 안됩니다.
    static MySingleton() 
    {
	theOneAndOnly = new MySingleton();
    }
    
    public static MySingleton TheOnly => theOneAndOnly;
    
    private MySingleton()
    {
    }
}

14. 초기화 코드가 중복되는 것을 최소화하세요.

더보기

여러 생성자 내에서 동일한 코드를 반복적으로 사용해야 하는 경우에는 private 헬퍼 메소드를 만들어서 중복되는 코드를 줄이는 것 보다는, 공용으로 사용할 수 있는 생성자를 작성하세요.

15. 불필요한 객체를 만들지 마세요.

더보기
  • 자주 사용(호출)되는 지역변수를 멤버 변수로 변경하세요. 예를 들면 매 프레임 호출되는 업데이트나 드로우 이벤트에서 객체를 생성하는 코드가 있을 때, 멤버 변수로 변경할 수 있다면 변경하세요.
  • 변경 불가능한 불변(immutable) 타입을 가공할 때, 가비지가 생성되는 것에 주의하세요. 대표적으로 String 객체도 불변 타입(객체 내의 문자열을 수정할 수 없음)임을 인지하면 좋습니다.

물론 String 의 문자열을 프로그래밍 하면서 계속 수정해왔던 경험 때문에 문자열을 수정할 수 있다고 착각할 수 있습니다. 

public static void Main(string[] args)
{
    string msg = "Hello, ";
    msg += userName;
    msg += ". Today is";
    msg += System.DateTime.Now.ToString();
}

 하지만 위와 같은 코드는 실제로는 다음과 같이 비효율적으로 작동합니다.

public static void Main(string[] args)
{
    string msg = "Hello, ";
    string tmp1 = new String(msg + userName);
    msg = tmp1;
    string tmp2 = new String(msg + ". Today is");
    msg = tmp2;
    string tmp3 = new String(msg + System.DateTime.Now.ToString());
    msg = tmp3;
}

그래서 만약 많은 문자열을 좀 더 복잡하게 다뤄야 하는 상황이라면 StringBuilder 클래스를 사용하면 좋습니다.

StringBuilder msg = new StringBuilder("Hello, ");
msg.Append(yourName);
msg.Append(". Today is ");
msg.Append(DateTime.Now.ToString());

string finalMsg = msg.ToString();

이러면 StringBuilder 클래스는 마지막에 ToString() 할때 한번만 문자열 객체를 생성합니다.

하지만 추가적으로 알아두면 좋을게 최근 컴파일러에서는 리터럴 문자열끼리 + 연산자로 문자열을 묶는 경우 알아서 내부적으로 최적화를 해줍니다. (+= 는 아님) 자세한 내용은 https://overworks.github.io/unity/2018/08/30/finding-best-string-concatenation.html 를 참고해주세요.

16. 생성자 내에서는 절대로 가상 함수를 호출하지 마세요.

더보기

객체가 완전히 생성되기 전에 가상 함수를 호출하면 정상 동작하지 않을 수 있습니다.

17. 표준 Dispose 패턴을 구현하세요.

더보기

 반드시 비관리 리소스 변수를 포함하는 경우 finalizer를 구현해야 합니다. 표준 Dispose 패턴은 다음과 같이 구현할 수 있습니다.

 

먼저, 최상위 클래스에서 다음 내용을 구현합니다.

 

  • IDisposable 인터페이스를 구현합니다.
  • 비관리 리소스 변수가 있다면 혹시 모르니 finalizer를 추가해서 방어적으로 동작하도록 합니다
  • Dispose와 finalizer의 실제 리소스 정리 작업을 수행하는 기능은 다른 가상 메소드에 위엄하여 동작하도록 작성합니다. (이래야 상속받은 클래스에서 이 가상 메소드를 재정의하여 고유의 리소스 정리 작업을 할 때 편합니다.)

 

상속받은 클래스는 다음 작업을 수행합니다.

 

  • 상위 클래스에 구현한 리소스 정리 작업을 수행하는 가상 함수를 오버라이드해서 재정의합니다. (이 때 상위 클래스의 가상 함수를 재호출 해야겠죠?)
  • 마찬가지로, 비관리 리소스 변수가 있다면 finalizer를 추가합니다.

 

또한, IDisposable.Dispose() 메소드에서는 다음 작업을 반드시 수행해야 합니다.

 

  1. 모든 비관리/관리 리소스를 정리합니다.
  2. 객체가 이미 정리되었음을 나타내기 위한 상태 플래그를 설정하고, 이미 정리되었을 경우 플래그를 체크해서 ObjectDisposed 예외를 발생시킵니다. (중복 정리를 막기 위해서)
  3. Dispose 호출시 finalizer가 별도로 호출되지 않게 하기 위해 GC.SuppressFinalize(this) 를 호출합니다.
  4. Dispose 메소드는 한번 이상 호출될 수 있으므로 여러 번 호출하더라도 동일하게 동작할 수 있도록 구현합니다.

18. 제네릭 활용 : 반드시 필요한 제약 조건만 설정하세요.

(작성중)