기술 자료

Unity Engine

Unity Engine

로우레벨 API를 사용하여 프로퍼티 방문자 생성

Learn how to use the IPropertyBagVisitor and IPropertyVisitor interfaces to create a property visitor from an example.
읽는 시간 6분최근 업데이트: 한 달 전

이 예시는 로우레벨 API를
IPropertyBagVisitor
및
IPropertyVisitor
인터페이스와 함께 사용하여 프로퍼티 방문자를 만드는 방법을 보여 줍니다. 이 예시는
PropertyVisitor
기본 클래스를 사용하여 프로퍼티 방문자를 생성하는 예시와 동일합니다.

개요 예시

이 예시에는 오브젝트의 현재 상태를 콘솔에 출력하는 프로퍼티 방문자를 만드는 단계별 명령이 포함되어 있습니다.
다음 유형이 있다고 가정해 봅시다.
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

방문자 생성

먼저
IPropertyBagVisitor
를 구현하는
DumpObjectVisitor
클래스를 만듭니다. 클래스 내에서 StringBuilder를 사용하여 오브젝트의 현재 상태를 나타내는 문자열을 만듭니다.
  1. IPropertyBagVisitor
    인터페이스를 구현하는
    DumpObjectVisitor
    클래스를 만듭니다.
  2. 클래스에
    StringBuilder
    필드를 추가합니다.
  3. StringBuilder
    를 지우고 들여쓰기 레벨을 재설정하는
    Reset
    메서드를 추가합니다.
  4. 오브젝트의 현재 상태를 나타내는 문자열을 반환하는
    GetDump
    메서드를 추가합니다.
    DumpObjectVisitor
    클래스는 다음과 같습니다.
    public class DumpObjectVisitor : IPropertyBagVisitor , IPropertyVisitor{ private const int k_InitialIndent = 0; private readonly StringBuilder m_Builder = new StringBuilder(); private int m_IndentLevel = k_InitialIndent; public void Reset() { m_Builder.Clear(); m_IndentLevel = k_InitialIndent; } public string GetDump() { return m_Builder.ToString(); }}

프로퍼티 가져오기

DumpObjectVisitor
클래스 내에서
IPropertyBagVisitor.Visit
메서드를 오버라이드하여 컨테이너 오브젝트의 프로퍼티를 반복합니다. 오브젝트 덤프 방문자에서 값을 표시하고 프로퍼티에 방문을 위임합니다.
this
를 사용하여 프로퍼티의
Accept
메서드를 호출하려면
IPropertyVisitor
인터페이스를 구현합니다. 이 인터페이스를 사용하면
PropertyVisitor
클래스의
VisitProperty
메서드와 유사하게 프로퍼티를 방문할 때 방문 동작을 지정할 수 있습니다.
  1. DumpObjectVisitor
    클래스 내에
    IPropertyBagVisitor.Visit
    및
    IPropertyVisitor.Visit
    메서드 오버라이드를 추가합니다.
    void IPropertyBagVisitor.Visit<TContainer>(IPropertyBag<TContainer> propertyBag, ref TContainer container){ foreach (var property in propertyBag.GetProperties(ref container)) { property.Accept(this, ref container); }} void IPropertyVisitor.Visit<TContainer, TValue>(Property<TContainer, TValue> property, ref TContainer container){ var value = property.GetValue(ref container); // Code goes here.}
  2. PropertyVisitor
    기본 클래스와 함께 사용되는
    IVisitPropertyAdapter
    어댑터는 방문자의 내부 상태에 액세스해야 하므로 해당 클래스 외부에서 사용할 수 없습니다. 하지만 필요한 정보가 포함된 도메인별 어댑터를 정의할 수 있습니다.
    DumpObjectVisito
    클래스 내에서 먼저 어댑터를 사용하도록
    IPropertyVisitor
    의 구현을 업데이트합니다.
    // Create the following methods to encapsulate the formatting of the message and display the value.public readonly struct PrintContext{ private StringBuilder Builder { get; } private string Prefix { get; } public string PropertyName { get; } public void Print<T>(T value) { Builder.AppendLine($"{Prefix}- {PropertyName} = {{{TypeUtility.GetTypeDisplayName(value?.GetType() ?? typeof(T))}}} {value}"); } public void Print(Type type, string value) { Builder.AppendLine($"{Prefix}- {PropertyName} = {{{TypeUtility.GetTypeDisplayName(type)}}} {value}"); } public PrintContext(StringBuilder builder, string prefix, string propertyName) { Builder = builder; Prefix = prefix; PropertyName = propertyName; }}public interface IPrintValue{}public interface IPrintValue<in T> : IPrintValue{ void PrintValue(in PrintContext context, T value);}public class DumpObjectVisitor : IPropertyBagVisitor , IPropertyVisitor , IPrintValue<Vector2> , IPrintValue<Color>{ public IPrintValue Adapter { get; set; } public DumpObjectVisitor() { // For simplicity Adapter = this; } void IPropertyVisitor.Visit<TContainer, TValue>(Property<TContainer, TValue> property, ref TContainer container) { // Here, we need to manually extract the value. var value = property.GetValue(ref container); var propertyName = GetPropertyName(property); // We can still use adapters, but we must manually dispatch the calls. if (Adapter is IPrintValue<TValue> adapter) { var context = new PrintContext(m_Builder, Indent, propertyName); adapter.PrintValue(context, value); return; } // Fallback behaviour here } void IPrintValue<Vector2>.PrintValue(in PrintContext context, Vector2 value) { context.Print(value); } void IPrintValue<Color>.PrintValue(in PrintContext context, Color value) { const string format = "F3"; var formatProvider = CultureInfo.InvariantCulture.NumberFormat; context.Print(typeof(Color), $"RGBA({value.r.ToString(format, formatProvider)}, {value.g.ToString(format, formatProvider)}, {value.b.ToString(format, formatProvider)}, {value.a.ToString(format, formatProvider)})"); }}
완성된 코드는 다음과 같습니다.
public readonly struct PrintContext{ // A context struct to hold information about how to print the property private StringBuilder Builder { get; } private string Prefix { get; } public string PropertyName { get; } // Method to print the value of type T with its associated property name public void Print<T>(T value) { Builder.AppendLine($"{Prefix}- {PropertyName} = {{{TypeUtility.GetTypeDisplayName(value?.GetType() ?? typeof(T))}}} {value}"); } // Method to print the value with a specified type and its associated property name public void Print(Type type, string value) { Builder.AppendLine($"{Prefix}- {PropertyName} = {{{TypeUtility.GetTypeDisplayName(type)}}} {value}"); } // Constructor to initialize the PrintContext public PrintContext(StringBuilder builder, string prefix, string propertyName) { Builder = builder; Prefix = prefix; PropertyName = propertyName; }}// Generic interface IPrintValue that acts as a marker interface for all print value adapterspublic interface IPrintValue{}// Generic interface IPrintValue<T> to define how to print values of type T// This interface is used as an adapter for specific types (Vector2 and Color in this case)public interface IPrintValue<in T> : IPrintValue{ void PrintValue(in PrintContext context, T value);}// DumpObjectVisitor class that implements various interfaces for property visiting and value printingprivate class DumpObjectVisitor : IPropertyBagVisitor, IPropertyVisitor, IPrintValue<Vector2>, IPrintValue<Color>{ // (Other members are omitted for brevity) public IPrintValue Adapter { get; set; } public DumpObjectVisitor() { // The Adapter property is set to this instance of DumpObjectVisitor // This means the current DumpObjectVisitor can be used as a print value adapter for Vector2 and Color. Adapter = this; } // This method is called when visiting a property bag (a collection of properties) void IPropertyBagVisitor.Visit<TContainer>(IPropertyBag<TContainer> propertyBag, ref TContainer container) { foreach (var property in propertyBag.GetProperties(ref container)) { // Call the Visit method of IPropertyVisitor to handle individual properties property.Accept(this, ref container); } } // This method is called when visiting each individual property of an object. // It tries to find a suitable adapter (IPrintValue<T>) for the property value type (TValue) and uses it to print the value. // If no suitable adapter is found, it falls back to displaying the value using its type name. void IPropertyVisitor.Visit<TContainer, TValue>(Property<TContainer, TValue> property, ref TContainer container) { // Here, we need to manually extract the value. var value = property.GetValue(ref container); var propertyName = GetPropertyName(property); // We can still use adapters, but we must manually dispatch the calls. // Try to find an adapter for the current property value type (TValue). if (Adapter is IPrintValue<TValue> adapter) { // If an adapter is found, create a print context and call the PrintValue method of the adapter. var context = new PrintContext(m_Builder, Indent, propertyName); adapter.PrintValue(context, value); return; } // Fallback behavior here - if no adapter is found, handle printing based on type information. var type = value?.GetType() ?? property.DeclaredValueType(); var typeName = TypeUtility.GetTypeDisplayName(type); if (TypeTraits.IsContainer(type)) m_Builder.AppendLine($"{Indent}- {propertyName} {{{typeName}}}"); else m_Builder.AppendLine($"{Indent}- {propertyName} = {{{typeName}}} {value}"); // Recursively visit child properties (if any). ++m_IndentLevel; if (null != value) PropertyContainer.Accept(this, ref value); --m_IndentLevel; } // Method from IPrintValue<Vector2> used to print Vector2 values void IPrintValue<Vector2>.PrintValue(in PrintContext context, Vector2 value) { // Simply use the Print method of PrintContext to print the Vector2 value. context.Print(value); } // Method from IPrintValue<Color> used to print Color values void IPrintValue<Color>.PrintValue(in PrintContext context, Color value) { const string format = "F3"; var formatProvider = CultureInfo.InvariantCulture.NumberFormat; // Format and print the Color value in RGBA format. context.Print(typeof(Color), $"RGBA({value.r.ToString(format, formatProvider)}, {value.g.ToString(format, formatProvider)}, {value.b.ToString(format, formatProvider)}, {value.a.ToString(format, formatProvider)})"); }}

하위 프로퍼티의 현재 상태 인쇄

기본적으로 데이터에서 방문자를 실행하면 해당 오브젝트에 대한 방문이 직접 시작됩니다. 모든 프로퍼티 방문자에 대해 오브젝트의 하위 등록 정보에 대한 방문을 시작하려면
PropertyContainer.Accept
메서드에
PropertyPath
를 전달합니다.
  1. 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()); }}
  2. Data
    오브젝트로
    PrintObjectDump
    메서드를 호출합니다. 이를 통해 원하는 출력을 얻습니다.

추가 리소스