PropertyVisitor
를 사용해 프로퍼티 방문자 생성이 예시에서는 PropertyVisitor
기본 클래스를 사용하여 프로퍼티 방문자를 생성하는 방법을 보여줍니다. IPropertyBagVisitor
및 IPropertyVisitor
인터페이스를 사용하는 동일한 예시의 경우 로우레벨 API를 사용하여 프로퍼티 방문자 생성을 참조하십시오.
이 예시에는 오브젝트의 현재 상태를 콘솔에 출력하는 프로퍼티 방문자를 생성하는 단계별 지침이 포함되어 있습니다.
다음과 같은 유형이 있다고 가정합니다.
public class Data
{
public string Name = "Henry";
public Vector2 Vec2 = Vector2.one;
public List<Color> Colors = new List<Color> { Color.green, Color.red };
public Dictionary<int, string> Dict = new Dictionary<int, string> {{5, "zero"}};
}
유틸리티 메서드 DebugUtilities
를 다음과 같이 생성합니다.
public static class DebugUtilities
{
public static void PrintObjectDump<T>(T value)
{
// Magic goes here.
}
}
다음과 같이 Data
오브젝트로 PrintObjectDump
메서드를 호출합니다.
DebugUtilities.PrintObjectDump(new Data());
콘솔에 다음과 같이 출력됩니다.
- Name {string} = Henry
- Vec2 {Vector2} = (1.00, 1.00)
- Colors {List<Color>}
- [0] = {Color} RGBA(0.000, 1.000, 0.000, 1.000)
- [1] = {Color} RGBA(1.000, 0.000, 0.000, 1.000)
- Dict {Dictionary<int, string>}
- [5] {KeyValuePair<int, string>}
- Key {int} = 5
- Value {string} = five
먼저 DumpObjectVisitor
클래스를 생성합니다. 클래스 안에서 StringBuilder를 사용하여 오브젝트의 현재 상태를 나타내는 문자열을 빌드합니다.
PropertyVisitor
에서 상속하는 DumpObjectVisitor
클래스를 생성합니다.
클래스에 StringBuilder
필드를 추가합니다.
StringBuilder
를 제거하고 인덴트 레벨을 재설정하는 Reset
메서드를 추가합니다.
오브젝트의 현재 상태를 나타내는 문자열을 반환하는 GetDump
메서드를 추가합니다.
완료된 클래스는 다음과 같습니다.
// `PropertyVisitor` is an abstract class that you must subclass from it.
public class DumpObjectVisitor: PropertyVisitor
{
private const int k_InitialIndent = 0;
private readonly StringBuilder m_Builder = new StringBuilder();
private int m_IndentLevel = k_InitialIndent;
private string Indent => new (' ', m_IndentLevel * 2);
public void Reset()
{
m_Builder.Clear();
m_IndentLevel = k_InitialIndent;
}
public string GetDump()
{
return m_Builder.ToString();
}
}
DumpObjectVisitor
클래스 안에서 VisitProperty
메서드를 오버라이드하여 오브젝트의 각 프로퍼티를 방문하고 프로퍼티 이름을 로깅합니다. PropertyVisitor
는 구현할 멤버가 필요하지 않으며, 기본적으로 모든 프로퍼티를 방문하기만 하고 아무런 작업도 수행하지 않습니다.
DumpObjectVisitor
클래스 안에서 다음 오버라이드 VisitProperty
메서드를 추가합니다.
protected override void VisitProperty<TContainer, TValue>(Property<TContainer, TValue> property, ref TContainer container, ref TValue value)
{
m_Builder.AppendLine($"- {property.Name}");
}
이제 최소한의 방문자만 있으므로 유틸리티 메서드를 구현할 수 있습니다. DebugUtilities
클래스에서 PrintObjectDump
메서드를 업데이트하여 새 DumpObjectVisitor
인스턴스를 생성하고, 이를 사용하여 지정된 오브젝트의 프로퍼티를 방문합니다.
public static class DebugUtilities
{
private static readonly DumpObjectVisitor s_Visitor = new ();
public static void PrintObjectDump<T>(T value)
{
s_Visitor.Reset();
// This is the main entry point to run a visitor.
PropertyContainer.Accept(s_Visitor, ref value);
Debug.Log(s_Visitor.GetDump());
}
}
이렇게 하면 다음과 같이 출력됩니다.
- Name
- Vec2
- Colors
- Dict
이전 섹션의 출력은 VisitProperty
메서드를 오버라이드할 때 오브젝트의 하위 프로퍼티를 자동으로 방문하지 않는다는 것을 나타냅니다. 하위 프로퍼티를 가져오려면 PropertyContainer.Accept
메서드를 사용하여 각 값에 방문자를 재귀적으로 적용합니다.
DebugUtilities
클래스 안에서 VisitProperty
메서드를 업데이트하여 중첩할 값에 방문자를 재귀적으로 적용합니다.
protected override void VisitProperty<TContainer, TValue>(Property<TContainer, TValue> property, ref TContainer container, ref TValue value)
{
m_Builder.AppendLine($"{Indent}- {property.Name}");
++m_IdentLevel;
// Apply this visitor recursively on the value to nest in.
if (null != value)
PropertyContainer.Accept(this, ref value);
--m_IdentLevel;
}
이렇게 하면 다음과 같이 출력됩니다.
- Name
- Vec2
- x
- y
- Colors
- 0
- r
- g
- b
- a
- 1
- r
- g
- b
- a
- Dict
- 5
- Key
- Value
그 다음에는 컬렉션 항목의 프로퍼티 이름과 각 프로퍼티의 타입 및 값을 가져옵니다.
특정 프로퍼티에는 특히 컬렉션 항목을 처리할 경우 특별한 이름이 있습니다. 다음은 프로퍼티 이름에 대한 규칙입니다.
이 구분을 더 명확하게 하려면 프로퍼티 이름을 대괄호로 묶습니다.
DumpObjectVisitor
클래스 안에서 다음 메서드를 추가합니다.
private static string GetPropertyName(IProperty property)
{
return property switch
{
// You can also treat `IListElementProperty`, `IDictionaryElementProperty`, and `ISetElementProperty` separately.
ICollectionElementProperty => $"[{property.Name}]",
_ => property.Name
};
}
VisitProperty
메서드를 업데이트하여 TypeUtility.GetTypeDisplayName
을 사용해 지정된 타입의 표시 이름을 검색합니다.
protected override void VisitProperty<TContainer, TValue>(Property<TContainer, TValue> property, ref TContainer container, ref TValue value)
{
var propertyName = GetPropertyName(property);
// Get the concrete type of the property or its declared type if value is null.
var typeName = TypeUtility.GetTypeDisplayName(value?.GetType() ?? property.DeclaredValueType());
m_Builder.AppendLine($"{Indent}- {propertyName} = {{{typeName}}} {value}");
++m_IndentLevel;
if (null != value)
PropertyContainer.Accept(this, ref value);
--m_IndentLevel;
}
이렇게 하면 다음과 같이 출력됩니다.
- Name = {string} Henry
- Vec2 = {Vector2} (1.00, 1.00)
- x = {float} 1
- y = {float} 1
- Colors = {List<Color>} System.Collections.Generic.List`1[UnityEngine.Color]
- [1] = {Color} RGBA(0.000, 1.000, 0.000, 1.000)
- r = {float} 0
- g = {float} 1
- b = {float} 0
- a = {float} 1
- [1] = {Color} RGBA(1.000, 0.000, 0.000, 1.000)
- r = {float} 1
- g = {float} 0
- b = {float} 0
- a = {float} 1
- Dict = {Dictionary<int, string>} System.Collections.Generic.Dictionary`2[System.Int32,System.String]
- [5] = {KeyValuePair<int, string>} [5, five]
- Key = {int} 5
- Value = {string} five
List<T>
는 ToString()
메서드를 오버라이드하지 않으므로 목록 값은 System.Collections.Generic.List1[UnityEngine.Color]
로 표시됩니다. 표시되는 정보의 양을 줄이려면 VisitProperty
를 업데이트하여 TypeTraits.IsContainer
유틸리티 메서드를 사용해 프리미티브, 열거형, 문자열과 같은 하위 프로퍼티를 포함하지 않는 타입에 대한 값만 표시합니다.
DumpObjectVisitor
클래스 안에서 VisitProperty
메서드를 업데이트하여 TypeTraits.IsContainer
를 사용해 값이 컨테이너 타입인지 확인합니다. 컨테이너 타입인 경우 값 없이 타입 이름이 표시됩니다. 컨테이너 타입이 아닌 경우 타입 이름과 값이 표시됩니다.
protected override void VisitProperty<TContainer, TValue>(Property<TContainer, TValue> property, ref TContainer container, ref TValue value)
{
var propertyName = GetPropertyName(property);
var type = value?.GetType() ?? property.DeclaredValueType();
var typeName = TypeUtility.GetTypeDisplayName(type);
// Only display the values for primitives, enums and strings.
if (TypeTraits.IsContainer(type))
m_Builder.AppendLine($"{Indent}- {propertyName} {{{typeName}}}");
else
m_Builder.AppendLine($"{Indent}- {propertyName} = {{{typeName}}} {value}");
++m_IndentLevel;
if (null != value)
PropertyContainer.Accept(this, ref value);
--m_IndentLevel;
}
이렇게 하면 다음과 같이 출력됩니다.
- Name = {string} Henry
- Vec2 {Vector2}
- x = {float} 1
- y = {float} 1
- Colors {List<Color>}
- [0] {Color}
- r = {float} 0
- g = {float} 1
- b = {float} 0
- a = {float} 1
- [1] {Color}
- r = {float} 1
- g = {float} 0
- b = {float} 0
- a = {float} 1
- Dict {Dictionary<int, string>}
- [5] {KeyValuePair<int, string>}
- Key = {int} 5
- Value = {string} five
팁:
표시되는 정보의 양을 줄이려면 다음 메서드를 사용하여 컬렉션 타입에 대한 Visit
전문화를 오버라이드하면 됩니다.
protected override void VisitCollection<TContainer, TCollection, TElement>(Property<TContainer, TCollection> property, ref TContainer container, ref TCollection value) {}
protected override void VisitList<TContainer, TList, TElement>(Property<TContainer, TList> property, ref TContainer container, ref TList value) {}
protected override void VisitDictionary<TContainer, TDictionary, TKey, TValue>(Property<TContainer, TDictionary> property, ref TContainer container, ref TDictionary value) {}
protected override void VisitSet<TContainer, TSet, TValue>(Property<TContainer, TSet> property, ref TContainer container, ref TSet value) {}
이는 VisitProperty
메서드와 유사하지만, 각 컬렉션 타입의 일반 파라미터를 노출합니다.
마지막으로, 타입별 오버라이드를 추가하여 Vector2
및 Color
타입을 보다 간결하게 표시합니다.
PropertyVisitor
를 IVisitPropertyAdapter
와 함께 사용합니다. 어댑터가 지정된 타입에 등록될 때마다 방문 중에 대상 타입이 발견되면 VisitProperty
메서드 대신 해당 어댑터가 호출됩니다.
DumpObjectVisitor
클래스 안에서 Vector2
및 Color
에 대한 IVisitPropertyAdapter
를 추가합니다.
public class DumpObjectVisitor
: PropertyVisitor
, IVisitPropertyAdapter<Vector2>
, IVisitPropertyAdapter<Color>
{
public DumpObjectVisitor()
{
AddAdapter(this);
}
void IVisitPropertyAdapter<Vector2>.Visit<TContainer>(in VisitContext<TContainer, Vector2> context, ref TContainer container, ref Vector2 value)
{
var propertyName = GetPropertyName(context.Property);
m_Builder.AppendLine($"{Indent}- {propertyName} = {{{nameof(Vector2)}}} {value}");
}
void IVisitPropertyAdapter<Color>.Visit<TContainer>(in VisitContext<TContainer, Color> context, ref TContainer container, ref Color value)
{
var propertyName = GetPropertyName(context.Property);
m_Builder.AppendLine($"{Indent}- {propertyName} = {{{nameof(Color)}}} {value}");
}
}
완성된 DumpObjectVisitor
클래스는 다음과 같습니다.
public class DumpObjectVisitor
: PropertyVisitor
, IVisitPropertyAdapter<Vector2>
, IVisitPropertyAdapter<Color>
{
private const int k_InitialIndent = 0;
// StringBuilder to store the dumped object's properties and values.
private readonly StringBuilder m_Builder = new StringBuilder();
private int m_IndentLevel = k_InitialIndent;
// Helper property to get the current indentation.
private string Indent => new (' ', m_IndentLevel * 2);
public DumpObjectVisitor()
{
// Constructor, it initializes the DumpObjectVisitor and adds itself as an adapter
// to handle properties of type Vector2 and Color.
AddAdapter(this);
}
// Reset the visitor, clearing the StringBuilder and setting indentation to initial level.
public void Reset()
{
m_Builder.Clear();
m_IndentLevel = k_InitialIndent;
}
// Get the string representation of the dumped object.
public string GetDump()
{
return m_Builder.ToString();
}
// Helper method to get the property name, handling collections and other property types.
private static string GetPropertyName(IProperty property)
{
return property switch
{
// If it's a collection element property, display it with brackets
ICollectionElementProperty => $"[{property.Name}]",
// For other property types, display the name as it is
_ => property.Name
};
}
// This method is called when visiting each property of an object.
// It determines the type of the value and formats it accordingly for display.
protected override void VisitProperty<TContainer, TValue>(Property<TContainer, TValue> property, ref TContainer container, ref TValue value)
{
var propertyName = GetPropertyName(property);
// Get the type of the value or property.
var type = value?.GetType() ?? property.DeclaredValueType();
var typeName = TypeUtility.GetTypeDisplayName(type);
// Only display the values for primitives, enums, and strings, and treat other types as containers.
if (TypeTraits.IsContainer(type))
m_Builder.AppendLine($"{Indent}- {propertyName} {{{typeName}}}");
else
m_Builder.AppendLine($"{Indent}- {propertyName} = {{{typeName}}} {value}");
// Increase indentation level before visiting child properties (if any).
++m_IndentLevel;
if (null != value)
PropertyContainer.Accept(this, ref value);
// Decrease indentation level after visiting child properties.
--m_IndentLevel;
}
// This method is a specialized override for Vector2 properties.
// It displays the property name and its value as a Vector2.
void IVisitPropertyAdapter<Vector2>.Visit<TContainer>(in VisitContext<TContainer, Vector2> context, ref TContainer container, ref Vector2 value)
{
var propertyName = GetPropertyName(context.Property);
m_Builder.AppendLine($"{Indent}- {propertyName} = {{{nameof(Vector2)}}} {value}");
}
// This method is a specialized override for Color properties.
// It displays the property name and its value as a Color.
void IVisitPropertyAdapter<Color>.Visit<TContainer>(in VisitContext<TContainer, Color> context, ref TContainer container, ref Color value)
{
var propertyName = GetPropertyName(context.Property);
m_Builder.AppendLine($"{Indent}- {propertyName} = {{{nameof(Color)}}} {value}");
}
}
데이터에 대한 방문자를 실행하면 기본적으로 지정된 오브젝트에 대한 방문이 바로 시작됩니다. 모든 프로퍼티 방문자의 경우 오브젝트의 하위 프로퍼티에 대한 방문을 시작하려면 PropertyPath
를 PropertyContainer.Accept
메서드에 전달하십시오.
DebugUtilities
메서드를 업데이트하여 선택적 PropertyPath
를 가져옵니다.
public static class DebugUtilities
{
private static readonly DumpObjectVisitor s_Visitor = new();
public static void PrintObjectDump<T>(T value, PropertyPath path = default)
{
s_Visitor.Reset();
if (path.IsEmpty)
PropertyContainer.Accept(s_Visitor, ref value);
else
PropertyContainer.Accept(s_Visitor, ref value, path);
Debug.Log(s_Visitor.GetDump());
}
}
Data
오브젝트로 PrintObjectDump
메서드를 호출합니다. 이렇게 하면 원하는 출력을 얻을 수 있습니다.