6 Hidden C# Features Every Unity Developer Should Know 영상 내용 정리

2025. 7. 8. 02:09·Unity

git-amend 유튜버의 6 Hidden C# Features Every Unity Developer Should Know 영상을 보니 괜찮은 기능들이 있어 정리해 보려고 합니다.

 

해당 영상은 아래 링크를 통해 확인 가능합니다.

 

1. Intelligent Logging


호출자 정보 어트리뷰트 CallerFilePath, CallerLineNumber, CallerMemberName 을 사용하여 로그에서 바로 어떤 함수에서 로그를 남겼는지 보여줄 수 있습니다.

호출자 어트리뷰트란 함수를 호출한 정보를 어트리뷰트에 알맞게 컴파일러가 알아서 넣어주는 기능입니다.

  • CallerFilePath : 호출자의 소스 파일 경로
  • CallerLineNumber: 호출자의 소스 파일 행 번호
  • CallerMemberName: 호출자의 메소드 또는 프로퍼티 이름

어트리뷰트를 사용하면 아래 스샷과 같이 로그를 찍을 수 있습니다.

 

예시 코드는 아래와 같습니다.

public class TestIntelligentLog
{
    public void StartTest(string testMessage)
    {
        Log(testMessage);
    }

    public void Log(
        string message,
        [CallerFilePath] string file = "",
        [CallerLineNumber] int line = 0,
        [CallerMemberName] string member = "")
    {
        Debug.Log($"<color=green>[{Path.GetFileName(file)}:{line} - {member}]</color> {message}");
    }
}

 

2. Default Interface Methods


C# 8.0부터 생긴 기능으로 인터페이스에서 Default 함수를 구현할 수 있습니다.

자식 클래스는 Default 함수를 override 할지 선택해서 할 수 있으며, 하지 않았을 경우 interface 의 함수를 호출합니다.

다만 해당 인터페이스의 타입으로 선언했을 때만 사용할 수 있으며, 자식 클래스 타입으로 선언했을 때는 Default 함수를 호출하지 못합니다.

 

예시 코드는 아래와 같습니다.

public class TestDefaultInterfaceMethod
{
    public void StartTest()
    {
        //LoggerA
        {
            LoggerA loggerA = new LoggerA();
            //loggerA.Log("Halo 0");  Compile Error
            loggerA.LogWarning("Halo 1");
            loggerA.LogError("Halo 2");
        }
            
        //LoggerA as ILooger
        {
            ILogger loggerA = new LoggerA();
            loggerA.Log("Halo 0");
            loggerA.LogWarning("Halo 1");
            loggerA.LogError("Halo 2");
        }
            
        //LoggerB
        {
            LoggerB loggerB = new LoggerB();
            loggerB.Log("Halo 0");
            loggerB.LogWarning("Halo 1");
            loggerB.LogError("Halo 2");
        }
            
        //LoggerB as ILooger
        {
            ILogger loggerB = new LoggerB();
            loggerB.Log("Halo 0");
            loggerB.LogWarning("Halo 1");
            loggerB.LogError("Halo 2");
        }
    }
        
    public interface ILogger
    {
        void Log(string message)
        {
            Debug.Log(message);
        }

        void LogWarning(string message);
        void LogError(string message);
    }

    public class LoggerA : ILogger
    {
        public void LogWarning(string message)
        {
            Debug.LogWarning($"LoggerA : {message}");
        }
            
        public void LogError(string message)
        {
            Debug.LogError($"LoggerA : {message}");
        }
    }
        
    public class LoggerB : ILogger
    {
        public void Log(string message)
        {
            Debug.Log($"LoggerB : {message}");
        }
            
        public void LogWarning(string message)
        {
            Debug.LogWarning($"LoggerB : {message}");
        }
            
        public void LogError(string message)
        {
            Debug.LogError($"LoggerB : {message}");
        }
    }
}

 

3. Custom Numeric Format Strings


숫자를 ToString을 사용해서 문자열로 리턴 할 때 인자로 포맷을 넣어서 서식을 적용할 수 있습니다.

그 중 ;(세미콜론)을 이용하여 양수, 음수, 0에 따라 출력되는 문자열에 서식을 적용할 수 있습니다.

 

예시 코드는 아래와 같습니다.

public class TestCustomNumericFormat
{
    public void StartTest()
    {
        Log(123);
        Log(-456);
        Log(0);
    }

    public void Log(int value)
    {
        var format = "##;**##**;This is Zero!";
        var formattedValue = value.ToString(format);
        Debug.Log($"Value : {value} -> FormattedValue: {formattedValue}");
    }
}

 

4. Deferred Enumeration


특정 컬렉션에서 조건에 맞게 필터링하여 값들을 가져오고 싶을 때가 있는데 그때 값들을 컬렉션에 추가해서 바로 가져오는 대신 yield 와 IEnumerable 를 사용하여 지연적으로 가져올 수 있습니다.

이렇게 사용하면 컬렉션 생성으로 인한 메모리 사용을 방지할 수 있습니다.

 

예시 코드는 아래와 같습니다.

public void StartTest0()
{
    var scores = new List<int> { 50, 125, 300, 80, 170 };
            
    //Origin
    {
        Debug.Log("Origin-------");

        foreach (var score in GetBigValues(scores))
        {
            Debug.Log($"High score : {score}");
        }
    }
            
    //Deferred Enumeration 0
    {
        Debug.Log("Deferred Enumeration 0-------");

        foreach (var score in GetBigValuesWithDeferredEnumeration0(scores))
        {
            Debug.Log($"High score : {score}");
        }
    }
            
    //Deferred Enumeration 1
    {
        Debug.Log("Deferred Enumeration 1-------");

        foreach (var score in GetBigValuesWithDeferredEnumeration1(scores))
        {
            Debug.Log($"High score : {score}");
        }
    }
}

public List<int> GetBigValues(List<int> input)
{
    var result = new List<int>();
    foreach (var val in input)
    {
        if (val > 100)
        {
            result.Add(val);
        }
    }

    return result;
}
        
public IEnumerable<int> GetBigValuesWithDeferredEnumeration0(List<int> input)
{
    foreach (var val in input)
    {
        if (val > 100)
        {
            yield return val;
        }
    }
}
        
public IEnumerable<int> GetBigValuesWithDeferredEnumeration1(List<int> input)
{
    return input.Where(val => val > 100);
}

 

다만 주의해야 할 점이 있습니다.

아래와 같이 fitlered 변수에 linq를 사용하여 값을 넣어 코드를 작성하면 fitlered 가 호출될 때마다 컬렉션을 생성합니다.

public void StartTest1()
{
    Debug.Log("StartTest1 ========================");
    var scores = new List<int> { 50, 125, 300, 80, 170 };
         
    // 호출할때마다 생성함
    var filtered = scores.Where(val =>
    {
        Debug.Log($"Filtering: {val}");
        return val > 100;
    });
            
    Debug.Log($"Count of big scores: {filtered.Count()}");
    Debug.Log($"Average of big scores: {filtered.Average()}");
}

 

때문에 아래와 같이 캐싱해서 사용해야 컬렉션을 중복해서 생성하지 않습니다.

public void StartTest1()
{
    Debug.Log("StartTest1 ========================");
    var scores = new List<int> { 50, 125, 300, 80, 170 };

		//캐싱됨
    var filtered = scores.Where(val =>
    {
        Debug.Log($"Filtering: {val}");
        return val > 100;
    }).ToList();
            
    Debug.Log($"Count of big scores: {filtered.Count()}");
    Debug.Log($"Average of big scores: {filtered.Average()}");
}

 

5. Implicit and Explicit Operators


직접 정의한 클래스나 구조체를 다른 타입으로 형 변환하도록 구현해야 할 때가 있습니다.

C# 은 implicit(암시적), explicit(명시적) 변환 연산자를 통해 해당 기능을 구현할 수 있습니다.

implicit 은 자동으로 형변환 해주는 연산자이며, explicit 은 캐스팅을 통해 형변환 해주는 연산자 입니다.

 

예시 코드는 아래와 같습니다.

public void StartTest()
{
            //explicit
    HealthForExplicit health1 = (HealthForExplicit)100;
    int hp1 = (int)health1;

            //implicit
    HealthForImplicit health2 = 100;
    int hp2 = health2;
}

struct HealthForExplicit
{
    public int value;

    public HealthForExplicit(int v)
    {
        value = v;
    }

    public static explicit operator int(HealthForExplicit health)
    {
        return health.value;
    }

    public static explicit operator HealthForExplicit(int v)
    {
        return new HealthForExplicit(v);
    }
}
        
struct HealthForImplicit
{
    public int value;

    public HealthForImplicit(int v)
    {
        value = v;
    }

    public static implicit operator int(HealthForImplicit health)
    {
        return health.value;
    }

    public static implicit operator HealthForImplicit(int v)
    {
        return new HealthForImplicit(v);
    }
}

 

6. Array Slicing


C# 8.0부터는 .. 연산자와 ^연산자를 사용하여 배열의 특정 부분을 잘라낼 수 있습니다.

.. 연산자 : 범위를 가져올 때 사용합니다.

ex) 1..4 : 인덱스 1부터 3까지

^ 연산자: 배열의 끝에서 특정 인덱스까지 요소를 가져올 때 사용합니다.

ex) ^2 : 끝에서 두 번째 인덱스 요소

 

예시 코드는 아래와 같으며,

public void StartTest()
{
    int[] scores = { 10, 20, 30, 40, 50 };

    int last = scores[^1];
    Debug.Log($"last : {last}");
            
    int[] last3 = scores[^3..];
    Debug.Log($"last3 : {last3}");
            
    int[] first2 = scores[..2];
    Debug.Log($"first2 : {first2}");
            
    int[] mid = scores[1..4];
    Debug.Log($"mid : {mid}");
}

 

해당 변수들의 값들은 아래와 같습니다.

 

참고


https://www.youtube.com/watch?v=ToWaslg-Vws

https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/attributes/caller-information

https://mentum.tistory.com/484

https://tech-interview.tistory.com/250

https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings#the--section-separator

https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/operators/user-defined-conversion-operators

https://www.csharpstudy.com/latest/CS8-indexing-slicing.aspx

'Unity' 카테고리의 다른 글

EditorWindow 안에 독립적인 SceneView 띄우기  (0) 2026.04.14
Unity Awaitable 을 Coroutine, UniTask 와 비교  (2) 2025.01.04
'Unity' 카테고리의 다른 글
  • EditorWindow 안에 독립적인 SceneView 띄우기
  • Unity Awaitable 을 Coroutine, UniTask 와 비교
ZoNaDak
ZoNaDak
돌고 돌아 게임개발자
  • ZoNaDak
    ZoNaDak 의 생각
    ZoNaDak
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 프로그래밍 일반
      • Unity
  • 블로그 메뉴

    • 홈
    • 태그
    • 미디어로그
    • 위치로그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    Unity
    abstract
    abstract class
    Dependency Injection
    Interface
    UI Toolkit
    DI
    PreviewRenderUtility
    Inversion of Control
    추상클래스
    Service Locator
    인터페이스
    Custom SceneView
    C#
    EditorWindow
    IOC
    IMGUIContainer
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
ZoNaDak
6 Hidden C# Features Every Unity Developer Should Know 영상 내용 정리
상단으로

티스토리툴바