[C#] CallerMemberAttribute 를 이용한 현재 메소드의 호출자 정보 알아오기 및 성능 비교 (feat. StackTrace)

728x90
반응형

C#을 이용해 개발 중이라면, 간혹 실행중인 코드에 대한 호출자정보를 찾아야 할 경우가 있습니다.

Method 이름, 실행중인 파일, 또는 행 번호와 같은 구체적인 호출자정보 일 수 있습니다.

 

저는 주로 이런한 내용이 필요했던 경우는

실행중인 프로그램내에서 발생한 Log를 남길 경우, 어느 파일, 어느 Method 에서 발생한 오류인지를 남기 위해서 필요했습니다. 좀 더 빠른 파악이 가능하고, 체계화된 프로세스를 이용중이라면 빠른 디버깅이 가능하기 때문입니다. 

이를 위해서 호출자정보를 찾던 도중 .NET Framework 4.5에 추가된 CallerMemberAttribute 에 대해서 알게 되었습니다.

 

 

기존에는 Reflection 을 이용해서 처리 하였지만, Reflection이 관련된 경우 현재 코드에 많은 양의 메타 데이터가 로드 및 처리되어야 하므로 성능이 중요한 문제가 될 수 있다는 글을 다수 발견 하였습니다. 

Microsoft MVP 인 Rick Strahl의 게시물에 따르면, 처리 시간이 약 4배 느려질수 있다고 합니다. (일부의 경우)

https://weblog.west-wind.com/posts/2004/Apr/11/Net-Reflection-and-Performance

 

.Net Reflection and Performance

Reflection often gets a bad rap for being slow. True it's much slower than direct access, but it's important to look at performance in the proper perspective. For many operations Reflection and 'evaluative' access to properties, fields and methods provides

weblog.west-wind.com

 


Reflection 을 이용한 처리

Common.Util.cs

public void GetMethod()
{
    SetLog(System.Reflection.MethodBase.GetCurrentMethod(), "Error xxxxxx")
}
public void SetLog( MethodBase method, string msg )
{
    NPLog.PrintError( $"{method.ReflectedType.FullName}.{method.Name} - {msg}" );
}

// 실행결과
Common.Util.GetMethod() - Error xxxxxxxxxxx

 


StackTrace 를 이용한 처리

Common.Util.cs

public void GetMethod()
{
    SetLog(new StackTrace().GetFrame(1).GetMethod(), "Error xxxxxx");
}
public void SetLog( MethodBase method, string msg )
{
    NPLog.PrintError( $"{method.ReflectedType.FullName}.{method.Name} - {msg}" );
}

// 실행결과
Common.Util.GetMethod() - Error xxxxxxxxxxx

 


CallerMemberAttribute 를 이용한 처리

Common.Util.cs

public void GetMethod()
{
    SetLog("Error xxxxxx");
}
public void SetLog( string message, [CallerFilePath] string filePath = null, 
                                    [CallerMemberName] string method = null, 
                                    [CallerLineNumber] int lineNumber = 0 )
{
    NLog.PrintError( $"{filePath} ({method} line is {lineNumber}) - " + message );
}


// 실행결과
D:\Project\SampleProject\Common\Util.cs (GetMethod()) line is 90 - Error xxxxx

 


속도 차이

 

참조 : http://rion.io/2017/11/18/knowing-when-to-reflect-with-caller-info-attributes/

위 링크를 인용하였지만, 1백만 이상 반복한 결과 이와 같은 속도 차이가 있다고 합니다.

또한 CallerMemberName은 메모리 측면에서 훨씬 더 효율적일뿐만 아니라 다른 두 옵션 중 하나보다 훨씬 빠릅니다.

 

 


추가

CallerMemberAttribute 를 사용하면, 속도면에서는 우수 하지만, Log로 남기고 보여지는 부분에서 약간의 가공을 하여야 보기가 좋습니다. 

간단한 파일 구조를 가지고 있다면 모르겠지만, 조금 세분화된 네임스페이스와 파일 구조를 가지고 있다면, 아래와 같이 가공을 하면 조금더 FilePath를 간략해 해서 볼수 있습니다. 

 

public void SetLog( string message, [CallerFilePath] string filePath = null, 
                                    [CallerMemberName] string method = null, 
                                    [CallerLineNumber] int lineNumber = 0 )
{
    NLog.PrintError( $"{filePath.Substring( filePath.IndexOf( System.Reflection.Assembly.GetExecutingAssembly().GetName().Name ) )} ({method} line is {lineNumber}) - " + message );
}

Common.Util - Error xxxxxx

 

 

 

728x90