ドキュメント

Unity Engine

Unity Engine

PropertyVisitor を使用してプロパティビジターを作成する

Follow this example to create a property visitor with the PropertyVisitor base class.
読み終わるまでの所要時間 14 分最終更新 1ヶ月前

この例では、
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 を使用してオブジェクトの現在の状態を表す文字列をビルドします。
  1. PropertyVisitor
    を継承する
    DumpObjectVisitor
    クラスを作成します。
  2. このクラスに
    StringBuilder
    フィールドを追加します。
  3. StringBuilder
    を消去しインデントレベルをリセットする
    Reset
    メソッドを追加します。
  4. オブジェクトの現在の状態を表す文字列を返す
    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
の実装にメンバーは必要ありません。デフォルトでは、各プロパティにアクセスするだけでアクションは実行しません。
  1. DumpObjectVisitor
    クラス内に、以下のオーバーライド
    VisitProperty
    メソッドを追加します。
    protected override void VisitProperty<TContainer, TValue>(Property<TContainer, TValue> property, ref TContainer container, ref TValue value){ m_Builder.AppendLine($"- {property.Name}");}
  2. 最小限のビジターを作成したら、ユーティリティメソッドを実装できます。
    DebugUtilities
    クラスで、新しい
    DumpObjectVisitor
    インスタンスを作成してからそれを使用して指定オブジェクトのプロパティを訪問するように
    PrintObjectDump
    メソッドを更新します。
    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

各プロパティの詳細を表示する

次に、コレクションアイテムのプロパティ名や、各プロパティのタイプと値を取得します。
特にコレクションアイテムを扱う場合、特定のプロパティには特別な名前があります。プロパティ名の規則は以下のとおりです。
  • リストアイテムの場合、名前はインデックスに相当します。
  • ディクショナリの場合、名前はキー値の文字バージョンから派生します。
  • セットの場合、名前は値の文字列バージョンに基づきます。
この区別をより明確にするには、プロパティ名を角かっこで囲みます。
  1. DumpObjectVisitor
    クラス内に、以下のメソッドを追加します。
    private static string GetPropertyName(IProperty property){ return property switch { // You can also treat `IListElementProperty`, `IDictionaryElementProperty`, and `ISetElementProperty` separately. ICollectionElementProperty => $"[{property.Name}]", _ => property.Name };}
  2. TypeUtility.GetTypeDisplayName
    を使用して特定タイプの表示名を取得するように
    VisitProperty
    メソッドを更新します。
    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]
として表示されます。表示される情報量を減らすには、
TypeTraits.IsContainer
ユーティリティメソッドを使用して、サブプロパティを含まないタイプ (プリミティブ、enum、文字列など) 場合にのみ値を表示するように
VisitProperty
を更新します。
DumpObjectVisitor
クラス内で、
TypeTraits.IsContainer
を使用して値がコンテナタイプかどうかを判断するように
VisitProperty
メソッドを更新します。値がコンテナタイプである場合は、その値なしでタイプ名を表示します。値がコンテナタイプでない場合は、タイプ名と値を表示します。
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}"); }}

サブプロパティの現在の状態の印刷

データに対してビジターを実行すると、デフォルトでは、指定されたオブジェクトに対して直接、アクセスを開始します。プロパティビジターに対して、オブジェクトのサブプロパティに対するアクセスを開始するには、
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
    メソッドを呼び出します。必要な出力を取得します。

追加リソース