Создание посетителя объекта с низкоуровневым APIs
В этом примере показано, как создать посетителя свойства с низкоуровневыми интерфейсами IPropertyBagVisitor и IPropertyVisitor. Результат такой же, как в примере Создание посетителя свойства с классом PropertyVisitor, но с посетителем, реализующим эти интерфейсы, а не таким, который производится из PropertyVisitor.
Создание классов данных и полезных классов
Создайте те же классы
DataиDebugUtilities, что и в примере Создайте посетителя свойства с классом PropertyVisitor в примере.-
Изменить
DebugUtilitiesкласс, чтобы использовать низкоуровневый визитор вместоPropertyVisitor-derived посетитель следующим образом:public static class DebugUtilities { private static readonly LowLevelVisitor 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()); } }
Создать посетителя
Создайте низкоуровневый класс-посетитель так:
Создать
LowLevelVisitorкласс, реализующийIPropertyVisitorиIPropertyBagVisitorинтерфейсы.Добавить
StringBuilderполе в класс. Это можно использовать для построения строки, представляющей текущее состояние объекта.Добавить метод
Reset, который очищаетStringBuilderи сбросает уровень отступов.-
Добавить метод
GetDumpкоторый возвращает строковое представление текущего состояния объекта.LowLevelVisitorкласс теперь выглядит так:public class LowLevelVisitor : 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(); } }
Получить свойства
Провести заседание Accept метод на свойство с использованием this, осуществление IPropertyVisitor интерфейс. Он позволяет задать поведение при обходе свойства — аналогично PropertyVisitorVisitProperty метод:
-
В течение
LowLevelVisitorкласс, переопределите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. } -
IVisitPropertyAdapterадаптеры, используемые сPropertyVisitorбазовому классу нужен доступ к внутреннему состоянию визитора, поэтому вне этого класса их использовать нельзя. Однако вы можете объявить предметно-ориентированные адаптеры, у которых есть нужные сведения. В том же файле, что иLowLevelVisitorкласс, определитеIPrintValueиIPrintValue<in T>интерфейсов для этих адаптеров иPrintContextструктуру, чтобы собрать сведения, нужные адаптерам для форматирования значения свойства, следующим образом:// Create the following struct with 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); } -
В течение
LowLevelVisitorкласс, переопределите метод интерфейса адаптера, чтобы создать адаптеры для конкретных типов —Vector2иColor, и обновить информацию об осуществленииIPropertyVisitorдля использования адаптера сначала:public class LowLevelVisitor : IPropertyBagVisitor , IPropertyVisitor , IPrintValue<Vector2> , IPrintValue<Color> { public IPrintValue Adapter { get; set; } public LowLevelVisitor() { // 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)})"); } }
Полный код посетителя
Полный код низкоуровневого класса-визитора и пользовательских адаптеров для Color и Vector2 выглядит следующим образом:
using UnityEngine;
using System.Globalization;
using System.Text;
using Unity.Properties;
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(System.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 adapters.
public 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);
}
// LowLevelVisitor class that implements various interfaces for property visiting and value printing.
public class LowLevelVisitor : IPropertyBagVisitor, IPropertyVisitor, IPrintValue<Vector2>, IPrintValue<Color>
{
private const int k_InitialIndent = 0;
private readonly StringBuilder m_Builder = new StringBuilder();
private int m_IndentLevel = k_InitialIndent;
public IPrintValue Adapter { get; set; }
public LowLevelVisitor()
{
// The Adapter property is set to this instance of LowLevelVisitor.
// This means the current LowLevelVisitor can be used as a print value adapter for Vector2 and Color.
Adapter = 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 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, m_IndentLevel.ToString(), 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($"{m_IndentLevel}- {propertyName} {{{typeName}}}");
else
m_Builder.AppendLine($"{m_IndentLevel}- {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)})");
}
}
Когда у вас есть полная версия этого кода в вашем LowLevelVisitor.cs файл, вызвать DebugUtilities.PrintObjectDump метод с экземпляром Data класс, чтобы убедиться, что он выводит в консоль то же самое, что и на финальном этапе Создание визитора свойств с помощью класса PropertyVisitor пример:
using UnityEngine;
public class MyMonoBehaviour : MonoBehaviour
{
void Start()
{
DebugUtilities.PrintObjectDump(new Data());
}
}