ドキュメント

Unity Engine

Unity Engine

低レベル API を使用してプロパティビジターを作成する

Learn how to use the IPropertyBagVisitor and IPropertyVisitor interfaces to create a property visitor from an example.
読み終わるまでの所要時間 8 分最終更新 1ヶ月前

この例では、
IPropertyBagVisitor
および
IPropertyVisitor
インターフェースで低レベル API を使用してプロパティビジターを作成する方法を示します。この例は、
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. 任意の
    PropertyPath
    を実行するように
    DebugUtilities
    メソッドを更新します。
    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
    メソッドを呼び出します。必要な出力を取得します。

追加リソース