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

> Learn how to use the IPropertyBagVisitor and IPropertyVisitor interfaces to create a property visitor from an example.

この例では、[`IPropertyBagVisitor`](/engine/6000.0/script-reference/unity-properties/i-property-bag-visitor.md) および [`IPropertyVisitor`](/engine/6000.0/script-reference/unity-properties/i-property-visitor.md) インターフェースで低レベル API を使用してプロパティビジターを作成する方法を示します。この例は、[`PropertyVisitor`](/engine/6000.0/script-reference/unity-properties/property-visitor.md) 基本クラスを使用してプロパティビジターを作成する [例](/engine/6000.0/manual/scripting/object-oriented-development/properties/property-visitors-propertyvisitor.md) と同じです。

## 例の概要

この例では、オブジェクトの現在の状態をコンソールに出力するプロパティビジターの作成手順を説明します。

以下の型があるとします。

```lang-csharp
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` を作成します。

```lang-csharp
public static class DebugUtilities
{
    public static void PrintObjectDump<T>(T value)
    {
        // Magic goes here.
    }
}
```

以下のように `Data` オブジェクトを使用して `PrintObjectDump` メソッドを呼び出します。

```lang-csharp
DebugUtilities.PrintObjectDump(new Data());
```

以下の内容がコンソールに出力されます。

```lang-text
- 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
```

## ビジターの作成##create-data-and-utility-classes

最初に、`IPropertyBagVisitor` を実装する `DumpObjectVisitor` クラスを作成します。クラス内で、[StringBuilder](https://learn.microsoft.com/en-us/dotnet/api/system.text.stringbuilder?view=net-7.0) を使用してオブジェクトの現在の状態を表す文字列を構築します。

1. `IPropertyBagVisitor` インターフェースを実装する `DumpObjectVisitor` クラスを作成します。

2. クラスに `StringBuilder` フィールドを追加します。

3. `StringBuilder` を消去し、インデントレベルをリセットする `Reset` メソッドを追加します。

4. オブジェクトの現在の状態の文字列表現を返す `GetDump` メソッドを追加します。

   `DumpObjectVisitor` クラスは以下のようになります。

   ```lang-csharp
   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`](/engine/6000.0/script-reference/unity-properties/i-property-bag-visitor/visit.md) メソッドをオーバーライドしてコンテナオブジェクトのプロパティーをループします。オブジェクトダンプビジターで値を表示し、プロパティへのアクセスをデリゲートします。

`this` を使用してプロパティの [`Accept`](/engine/6000.0/script-reference/unity-properties/property-2/accept.md) メソッドを呼び出すには、[`IPropertyVisitor`](/engine/6000.0/script-reference/unity-properties/i-property-visitor.md) インターフェースを実装します。このインターフェースでは、[`PropertyVisitor`](/engine/6000.0/script-reference/unity-properties/property-visitor.md) クラスの [`VisitProperty`](/engine/6000.0/script-reference/unity-properties/property-visitor/visit-property.md) メソッドと同様に、プロパティにアクセスしたときの動作を指定できます。

1. `DumpObjectVisitor` クラス内に、`IPropertyBagVisitor.Visit` メソッドと `IPropertyVisitor.Visit` メソッドのオーバーライドを追加します。

   ```lang-csharp
   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`](/engine/6000.0/script-reference/unity-properties/property-visitor.md) 基本クラスで使用される `IVisitPropertyAdapter` アダプターは、ビジターの内部状態にアクセスする必要があるため、そのクラス外部では使用できません。ただし、必要な情報を持つドメイン固有のアダプターを定義することができます。`DumpObjectVisito` クラス内で、最初にアダプターを使用するように [`IPropertyVisitor`](/engine/6000.0/script-reference/unity-properties/i-property-visitor.md) の実装を更新します。

   ```lang-csharp
   // 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)})");
       }
   }
   ```

完成したコードは以下のようになります。

```lang-csharp
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 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);
}

// DumpObjectVisitor class that implements various interfaces for property visiting and value printing
private 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`](/engine/6000.0/script-reference/unity-properties/property-container/accept.md.md) メソッドに [`PropertyPath`](/engine/6000.0/script-reference/unity-properties/property-path.md.md) を渡します。

1. 任意の `PropertyPath` を実行するように `DebugUtilities` メソッドを更新します。

   ```lang-csharp
   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` メソッドを呼び出します。[必要な出力](#desired-output)を取得します。

## 追加リソース##get-the-properties

* [プロパティビジター](/engine/6000.0/manual/scripting/object-oriented-development/properties/property-visitors.md)
* [プロパティバッグ](/engine/6000.0/manual/scripting/object-oriented-development/properties/property-bags.md)
* [プロパティパス](/engine/6000.0/manual/scripting/object-oriented-development/properties/property-paths.md)
* [`PropertyVisitor` を使用してプロパティビジターを作成する](/engine/6000.0/manual/scripting/object-oriented-development/properties/property-visitors-propertyvisitor.md)
