2022. 12. 30. 17:23ㆍC#
DebuggerDisplay
* 왜 필요함?
아래와 같이 냉장고를 추상화한 클래스가 있다.
public class Refrigerator
{
/// <summary>
/// 설정 온도 (도달해야 할 온도)
/// </summary>
public float SetPoint { get; } = 3.0f;
/// <summary>
/// 공급 온도 (설정 온도에 도달하기 위해 공급되는 바람의 온도)
/// </summary>
public float SupplyTemp { get; } = 2.0f;
/// <summary>
/// 순환 온도 (냉장고를 한 바퀴 순환하고 되돌아오는 바람의 온도)
/// </summary>
public float ReturnTemp { get; } = 3.2f;
/// <summary>
/// 냉장고 내부의 습도
/// </summary>
public float Humid { get; } = 72.5f;
public Refrigerator(float setPoint, float supplyTemp, float returnTemp, float humid)
{
SetPoint = setPoint;
SupplyTemp = supplyTemp;
ReturnTemp = returnTemp;
Humid = humid;
}
}
코드가 길어보이지만 주석 때문에 그런거고 뭐 별거 없다.
프로퍼티 4개에 프로퍼티 4개를 초기화할 수 있는 생성자 그게 전부다.
이제 아래의 코드처럼 위의 냉장고 클래스를 사용하는 코드를 만들어보자.
List<Refrigerator> reefers = new List<Refrigerator>();
reefers.Add(new Refrigerator(0.0f, -0.5f, 1.0f, 40));
reefers.Add(new Refrigerator(1.1f, -1.6f, 1.8f, 50));
reefers.Add(new Refrigerator(0.2f, -0.7f, 1.2f, 55));
reefers.Add(new Refrigerator(0.3f, -1.0f, 1.0f, 48));
reefers.ForEach(s => Console.WriteLine(s));
디버그모드에서 reefer 값을 조사식에서 확인해보면 아래와 같이 나오는 것을 볼 수 있다.
단순히 타입에 대한 내용만이 나오고 그 이상은 뭐 없다...
그 이상 보려고 하면 아래처럼 세부 내용을 보기 위해 화살표를 한번 클릭해줘야 나온다.
아니 뭐 클릭해주면 되는거 아니냐? 라고 할 수 있는데... 생각해보자. 저거 리스트인데
리스트 안에서 특정 값을 찾기 위해 일일이 다 클릭해서 보는건 얼마나 비효율적인가?
그냥 한번 아래의 리스트와 비교해보면 확 와 닿을 것이다.
디버깅시에 굉장히 편해진 것을 알 수 있다.
큰 건 아니지만 이러한 것들이 전부 유지보수비용을 낮추는데 알게 모르게 지대한 역할을 한다.
이번 주제는 위와 같은 것을 하기 위함이다.
* 그래서 어찌하면 됨?
2가지 방법이 있다.
첫번째로는 ToString() 를 오버라이드 하는 것이고 두번째로는 바로 이번 주제인 DebuggerDisplay를 쓰는 것
첫번째 방법은 간단한데 그냥 클래스 내부에 ToString()를 오버라이드 하기만 하면 된다.
위 처럼 냉장고 클래스에 ToString()를 오버라이드 한다.
그 이후 디버그모드에서 조사식을 보면 아래처럼 출력되는 것을 확인할 수 있다.
그런데 첫번째 방법은 한 가지 문제가 있다.
위 방법으로 모든 게 해결될 것이었으면 DebuggerDisplay 기능이 존재할 필요도 없었겠지
ToString() 는 객체를 외부에 출력할 때 사용되는 함수이기 때문에 콘솔에 출력해보면 아래와 같이 디버그모드의 조사식에서 보는 형태 그대로 출력된다.
원했던 것이 이거라면 그대로 사용해도 되는데, 디버그모드의 조사식에서 보는 형태와, 외부에 출력하는 형태를 다르게 가져가고 싶다면 반드시 DebuggerDisplay를 사용해야만 한다.
그럼 이제 두번째 방법인 DebuggerDisplay로 해결해보기로 하자.
우선 Refrigerator 에 커서를 두고 Alt + Enter를 입력한다.
그러면 아래 메뉴가 나올텐데, 메뉴에서 DebuggerDisplay 특성을 추가합니다를 선택하도록 하자.
그러면 아래와 같이 DebuggerDisplay 애트리뷰트가 추가된 것을 볼 수 있는데, GetDebuggerDisplay() 에 원하는 조사식 포맷을 작성하면 된다.
그리고 실행해보면 아래처럼 디버그모드에서 조사식이 바뀐 것을 확인할수 있고, 콘솔에 출력했을 때는 포맷이 변경되지 않음을 확인할 수 있다.
* 조금 더...
위 처럼 사용해도 충분하지만 코드 유지보수비용을 낮추기 위한 방법으로 조금 더 깊이 생각해보도록 하자.
위의 코드는 지금은 문제가 없다.
그런데 세상 모든게 변하듯이 훗날 위의 클래스에 새로운 멤버가 추가될수도, 기존 멤버가 수정되거나 삭제될 수도 있다.
그럴 경우 GetDebuggerDisplay() 는 이에 대한 영향을 받을 것이고 그것은 즉 GetDebuggerDisplay 내부도 변경되어야 함을 의미한다. (즉 코드 변경 시 비용이 높아지는 것이다.)
그럼 이를 해결할 방법은 없을까?
내부멤버 구성이 변화하더라도 GetDebuggerDisplay가 변화된 멤버구성을 스스로 감지해서 출력해주면 될텐데...
이쯤되면 아마 눈치챘을 것이다.
그렇다 Reflection 을 쓰면 된다.
GetDebuggerDisplay() 내부를 아래와 같이 변경하도록 하자.
private string GetDebuggerDisplay()
{
string result = string.Empty;
foreach(var field in this.GetType().GetFields())
result += $"{field.Name}: {field.GetValue(this)} ";
foreach(var property in this.GetType().GetProperties())
result += $"{property.Name}: {property.GetValue(this)} ";
return result;
}
에러가 난다면 에러가 나는 토큰에 커서를 두고 Alt + Enter를 눌러서 namespace를 추가해주도록 하자.
실행해보면 이전과 별반 다를바 없지만, 주요한 차이점은 이제 GetDebuggerDisplay()는 클래스 내부 멤버구성이 변경되면 이를 자동으로 감지하여 출력하기 때문에 클래스 내부 멤버구성이 변경되더라도 GetDebuggerDisplay()는 어떠한 영향도 받지 않는다는 점이다.
'C#' 카테고리의 다른 글
[C#] is 패턴 매칭, is not 패턴 매칭 에 대해 알아보자 (0) | 2023.11.18 |
---|---|
[C#] 비동기 프로그래밍: Task.Run과 바로 호출의 차이점 (3) | 2023.10.24 |
[C#] 비동기 프로그래밍(APM)에 대해 알아보자 (0) | 2023.03.21 |
[C#] 열거형 값을 문자열로 사용해보자 (0) | 2022.12.30 |
[C#] 외부함수를 멤버함수처럼 사용하기 (0) | 2022.12.26 |