次の方法で共有


カスタム依存関係プロパティ

ここでは、C++、C#、または Visual Basic を使用して、Windows ランタイム アプリの独自の依存関係プロパティを定義して実装する方法について説明します。 アプリ開発者とコンポーネント作成者がカスタム依存関係プロパティを作成する理由を示します。 カスタム依存関係プロパティの実装手順と、依存関係プロパティのパフォーマンス、使いやすさ、または汎用性を向上させるベスト プラクティスについて説明します。

[前提条件]

依存関係プロパティの概要を読み、既存 の依存関係プロパティ のコンシューマーの観点から依存関係プロパティを理解していることを前提としています。 このトピックの例に従うには、XAML について理解し、C++、C#、または Visual Basic を使用して基本的な Windows ランタイム アプリを記述する方法についても理解する必要があります。

依存関係プロパティとは

プロパティのスタイル、データ バインディング、アニメーション、既定値をサポートするには、依存関係プロパティとして実装する必要があります。 依存関係プロパティの値は、クラスのフィールドとして格納されず、xaml フレームワークによって格納され、 DependencyProperty.Register メソッドを呼び出してプロパティが Windows ランタイム プロパティ システムに登録されるときに取得されるキーを使用して参照されます。 依存関係プロパティは、 DependencyObject から派生した型でのみ使用できます。 ただし、 DependencyObject はクラス階層では非常に高いため、UI とプレゼンテーションのサポートを目的としたクラスの大半は依存関係プロパティをサポートできます。 依存関係プロパティの詳細と、このドキュメントで説明するために使用される用語と規則の一部については、「 依存関係プロパティの概要」を参照してください。

Windows ランタイムの依存関係プロパティの例としては、 Control.BackgroundFrameworkElement.WidthTextBox.Text などがあります。

規則では、クラスによって公開される各依存関係プロパティには、依存関係プロパティの識別子を提供する、同じクラスで公開される DependencyProperty 型の対応するパブリック静的読み取り専用プロパティがあります。 識別子の名前は、依存関係プロパティの名前という規則に従い、名前の末尾に文字列 "Property" が追加されます。 たとえば、Control.Background プロパティの対応する DependencyProperty 識別子は Control.BackgroundProperty です。 識別子には、登録された依存関係プロパティに関する情報が格納され、 SetValue の呼び出しなど、依存関係プロパティに関連する他の操作に使用できます。

プロパティ ラッパー

依存関係プロパティには、通常、ラッパー実装があります。 ラッパーがない場合、プロパティを取得または設定する唯一の方法は、依存関係プロパティ ユーティリティ メソッド GetValueSetValue を使用し、パラメーターとして識別子を渡すことです。 これは、表向きはプロパティであるものに対して、かなり不自然な使用です。 ただし、ラッパーを使用すると、依存関係プロパティを参照するコードやその他のコードで、使用している言語に自然な単純なオブジェクト プロパティ構文を使用できます。

カスタム依存関係プロパティを自分で実装し、それをパブリックで簡単に呼び出す場合は、プロパティ ラッパーも定義します。 プロパティ ラッパーは、依存関係プロパティに関する基本情報をリフレクションまたは静的分析プロセスに報告する場合にも役立ちます。 具体的には、ラッパーは ContentPropertyAttribute などの属性を配置する場所です。

依存関係プロパティとしてプロパティを実装する場合

クラスにパブリック読み取り/書き込みプロパティを実装する場合は、クラスが DependencyObject から派生している限り、プロパティを依存関係プロパティとして機能させるオプションがあります。 プライベートフィールドでプロパティをバックアップするという一般的な手法が十分である場合があります。 カスタム プロパティを依存関係プロパティとして定義することは、必ずしも必要または適切であるとは限りません。 選択は、あなたのプロパティがサポートすることを意図するシナリオによって異なります。

Windows ランタイムまたは Windows ランタイム アプリの 1 つ以上の機能をサポートする場合は、プロパティを依存関係プロパティとして実装することを検討してください。

依存関係プロパティを定義するためのチェックリスト

依存関係プロパティの定義は、一連の概念と考えることができます。 実装内の 1 行のコードで複数の概念に対処できるため、これらの概念は必ずしも手続き型の手順ではありません。 この一覧では、概要を簡単に説明します。 各概念については、このトピックの後半で詳しく説明します。また、いくつかの言語でコード例を示します。

  • プロパティ名をプロパティ システム (Register の呼び出し) に登録し、所有者の型とプロパティ値の型を指定します。
    • プロパティ メタデータを必要とする Register に必要なパラメーターがあります。 これに null を 指定するか、プロパティ変更の動作、または ClearValue を呼び出して復元できるメタデータベースの既定値を指定する場合は、 PropertyMetadata のインスタンスを指定します。
  • 所有者型のパブリック静的読み取り専用プロパティ メンバーとして DependencyProperty 識別子を定義します。
  • 実装する言語で使用されるプロパティ アクセサー モデルに従って、ラッパー プロパティを定義します。 ラッパー プロパティ名は、Register で使用した名前文字列と一致する必要があります。 GetValueSetValue を呼び出し、独自のプロパティの識別子をパラメーターとして渡すことによって、ラッパーをラップする依存関係プロパティに接続する get アクセサーと set アクセサーを実装します。
  • (省略可能) ContentPropertyAttribute などの属性をラッパーに配置します。

カスタム添付プロパティを定義する場合は、通常、ラッパーを省略します。 代わりに、XAML プロセッサで使用できる別のスタイルのアクセサーを記述します。 「カスタム添付プロパティ」を参照してください。

プロパティの登録

プロパティを依存関係プロパティにするには、Windows ランタイム プロパティ システムによって管理されているプロパティ ストアにプロパティを登録する必要があります。 プロパティを登録するには、 Register メソッドを呼び出します。

Microsoft .NET 言語 (C# および Microsoft Visual Basic) の場合は、クラスの本文 (クラス内、メンバー定義外) 内で Register を呼び出します。 識別子は、戻り値として Register メソッド呼び出しによって提供されます。 Register 呼び出しは、通常、静的コンストラクターとして、またはクラスの一部として DependencyProperty 型のパブリック静的読み取り専用プロパティの初期化の一部として行われます。 このプロパティは、依存関係プロパティの識別子を公開します。 Register 呼び出しの例を次に示します。

識別子プロパティ定義の一部として依存関係プロパティを登録することは一般的な実装ですが、クラスの静的コンストラクターに依存関係プロパティを登録することもできます。 依存関係プロパティを初期化するために複数のコード行が必要な場合は、この方法が適しています。

C++/CX には、ヘッダーとコード ファイルの間で実装を分割する方法のオプションがあります。 一般的な分割は、public static識別子自体をヘッダーのプロパティとして宣言し、get実装があるが、setはない。 get 実装は、初期化されていない DependencyProperty インスタンスであるプライベート フィールドを参照します。 ラッパーおよびそのgetsetの実装を宣言することもできます。 この場合、ヘッダーには最小限の実装が含まれています。 ラッパーに Windows ランタイム属性が必要な場合は、ヘッダー内の属性も必要です。 コード ファイル内の、アプリが初めて初期化されるときにのみ実行されるヘルパー関数に、Register呼び出しを配置します。 Register の戻り値を使用して、ヘッダーで宣言した静的で初期化されていない識別子を入力します。これは、実装ファイルのルート スコープで最初に nullptr に設定しました。

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(Nothing))
// ImageWithLabelControl.idl
namespace ImageWithLabelControlApp
{
    runtimeclass ImageWithLabelControl : Windows.UI.Xaml.Controls.Control
    {
        ImageWithLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

// ImageWithLabelControl.h
...
struct ImageWithLabelControl : ImageWithLabelControlT<ImageWithLabelControl>
{
...
public:
    static Windows::UI::Xaml::DependencyProperty LabelProperty()
    {
        return m_labelProperty;
    }

private:
    static Windows::UI::Xaml::DependencyProperty m_labelProperty;
...
};

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
...
//.h file
//using namespace Windows::UI::Xaml::Controls;
//using namespace Windows::UI::Xaml::Interop;
//using namespace Windows::UI::Xaml;
//using namespace Platform;

public ref class ImageWithLabelControl sealed : public Control
{
private:
    static DependencyProperty^ _LabelProperty;
...
public:
    static void RegisterDependencyProperties();
    static property DependencyProperty^ LabelProperty
    {
        DependencyProperty^ get() {return _LabelProperty;}
    }
...
};

//.cpp file
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml.Interop;

DependencyProperty^ ImageWithLabelControl::_LabelProperty = nullptr;

// This function is called from the App constructor in App.xaml.cpp
// to register the properties
void ImageWithLabelControl::RegisterDependencyProperties()
{
    if (_LabelProperty == nullptr)
    {
        _LabelProperty = DependencyProperty::Register(
          "Label", Platform::String::typeid, ImageWithLabelControl::typeid, nullptr);
    }
}

C++/CX コードの場合、 DependencyProperty を表示するプライベート フィールドとパブリック読み取り専用プロパティがある理由は、依存関係プロパティを使用する他の呼び出し元も、識別子をパブリックにする必要があるプロパティ システム ユーティリティ API を使用できるようにするためです。 識別子を非公開のままにした場合、ユーザーはこれらのユーティリティ API を使用できません。 このような API やシナリオの例としては、 GetValue または SetValue by choice、 ClearValueGetAnimationBaseValueSetBindingSetter.Property などがあります。 Windows ランタイム メタデータ ルールではパブリック フィールドが許可されないため、このためにパブリック フィールドを使用することはできません。

依存関係プロパティ名の規則

依存関係プロパティには名前付け規則があります。例外的な状況以外のすべての状況でそれらに従ってください。 依存関係プロパティ自体には、 Register の最初のパラメーターとして指定された基本名 (前の例では "Label" ) があります。 名前は登録する型ごとに一意である必要があり、一意性の要件は継承されたメンバーにも適用されます。 基本型を通じて継承された依存関係プロパティは、既に登録型の一部と見なされます。継承されたプロパティの名前を再度登録することはできません。

Warnung

ここで指定する名前には、任意の言語のプログラミングで有効な任意の文字列識別子を指定できますが、通常は XAML でも依存関係プロパティを設定できるようにする必要があります。 XAML で設定するには、選択するプロパティ名が有効な XAML 名である必要があります。 詳細については、XAML の 概要に関するページを参照してください。

識別子プロパティを作成するときに、登録したプロパティの名前をサフィックス "Property" ("LabelProperty" など) と組み合わせます。 このプロパティは依存関係プロパティの識別子であり、独自のプロパティ ラッパーで 行う SetValue 呼び出しと GetValue 呼び出しの入力として使用されます。 また、プロパティ システムおよび {x:Bind} などの他の XAML プロセッサでも使用されます。

ラッパーの実装

プロパティ ラッパーは get 実装でGetValue を呼び出し、set 実装では SetValue を呼び出す必要があります。

Warnung

例外を除くすべての状況で、ラッパー実装では GetValue および SetValue 操作のみを実行する必要があります。 それ以外の場合は、XAML を使用してプロパティを設定する場合と、コードを使用して設定した場合とは異なる動作が得られます。 効率を高める目的で、XAML パーサーは依存関係プロパティを設定するときにラッパーをバイパスします。 SetValue を使用してバッキング ストアと通信します。

public String Label
{
    get { return (String)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}
Public Property Label() As String
    Get
        Return DirectCast(GetValue(LabelProperty), String)
    End Get
    Set(ByVal value As String)
        SetValue(LabelProperty, value)
    End Set
End Property
// ImageWithLabelControl.h
...
winrt::hstring Label()
{
    return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
}

void Label(winrt::hstring const& value)
{
    SetValue(m_labelProperty, winrt::box_value(value));
}
...
//using namespace Platform;
public:
...
  property String^ Label
  {
    String^ get() {
      return (String^)GetValue(LabelProperty);
    }
    void set(String^ value) {
      SetValue(LabelProperty, value);
    }
  }

カスタム依存関係プロパティのプロパティ メタデータ

プロパティ メタデータが依存関係プロパティに割り当てられると、プロパティ所有者型またはそのサブクラスのすべてのインスタンスについて、同じメタデータがそのプロパティに適用されます。 プロパティ メタデータでは、次の 2 つの動作を指定できます。

  • プロパティ システムがプロパティのすべてのケースに割り当てる既定値。
  • プロパティ値の変更が検出されるたびに、プロパティ システム内で自動的に呼び出される静的コールバック メソッド。

プロパティ メタデータを使用した Register の呼び出し

DependencyProperty.Register を呼び出す前の例では、propertyMetadata パラメーターに null 値を渡しました。 依存関係プロパティで既定値を指定したり、プロパティ変更コールバックを使用したりできるようにするには、これらの機能の一方または両方を提供する PropertyMetadata インスタンスを定義する必要があります。

通常は、DependencyProperty.Register のパラメーター内で、インラインで作成されたインスタンスとして PropertyMetadata を指定します。

CreateDefaultValueCallback 実装を定義する場合は、PropertyMetadata コンストラクターを呼び出して PropertyMetadata インスタンスを定義するのではなく、ユーティリティ メソッド PropertyMetadata.Create を使用する必要があります。

次の例では、PropertyChangedCallback 値を持つ PropertyMetadata インスタンスを参照して、前に示した DependencyProperty.Register の例を変更します。 "OnLabelChanged" コールバックの実装については、このセクションの後半で説明します。

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null,new PropertyChangedCallback(OnLabelChanged))
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(
        Nothing, new PropertyChangedCallback(AddressOf OnLabelChanged)))
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr, Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...
DependencyProperty^ ImageWithLabelControl::_LabelProperty =
    DependencyProperty::Register("Label",
    Platform::String::typeid,
    ImageWithLabelControl::typeid,
    ref new PropertyMetadata(nullptr,
      ref new PropertyChangedCallback(&ImageWithLabelControl::OnLabelChanged))
    );

既定値

依存関係プロパティの既定値を指定して、プロパティが設定されていないときに常に特定の既定値を返すようにすることができます。 この値は、そのプロパティの型に固有の既定値とは異なる場合があります。

既定値が指定されていない場合、依存関係プロパティの既定値は参照型の場合は null、値型または言語プリミティブの型の既定値 (整数の場合は 0、文字列の場合は空の文字列など) です。 既定値を設定する主な理由は、プロパティで ClearValue を呼び出すとこの値が復元されるためです。 プロパティごとに既定値を設定する方が、特に値型の場合、コンストラクターで既定値を設定するよりも便利な場合があります。 ただし、参照型の場合は、既定値を設定しても意図しないシングルトン パターンが作成されないことを確認してください。 詳細については、このトピックで後述する ベスト プラクティス を参照してください。

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...

既定値としての UnsetValue を登録しないでください。 そうすると、プロパティ のコンシューマーが混乱し、プロパティ システム内で意図しない結果が発生します。

CreateDefaultValueCallback

一部のシナリオでは、複数の UI スレッドで使用されるオブジェクトの依存関係プロパティを定義しています。 これは、複数のアプリで使用されるデータ オブジェクトや、複数のアプリで使用するコントロールを定義する場合に当てはまる可能性があります。 プロパティを登録したスレッドに関連付けられている既定値インスタンスではなく 、CreateDefaultValueCallback 実装を提供することで、異なる UI スレッド間でオブジェクトの交換を有効にすることができます。 基本的に、 CreateDefaultValueCallback は既定値のファクトリを定義します。 CreateDefaultValueCallback によって返される値は、オブジェクトを使用している現在の UI CreateDefaultValueCallback スレッドに常に関連付けられます。

CreateDefaultValueCallback を指定するメタデータを定義するには、PropertyMetadata.Create を呼び出してメタデータ インスタンスを返す必要があります。PropertyMetadata コンストラクターには、CreateDefaultValueCallback パラメーターを含むシグネチャがありません。

CreateDefaultValueCallback の一般的な実装パターンは、新しい DependencyObject クラスを作成し、DependencyObject の各プロパティの特定のプロパティ値を目的の既定値に設定し、CreateDefaultValueCallback メソッドの戻り値を使用してオブジェクト参照として新しいクラスを返します。

プロパティ変更コールバック メソッド

プロパティ変更コールバック メソッドを定義して、プロパティの他の依存関係プロパティとの相互作用を定義したり、プロパティが変更されるたびにオブジェクトの内部プロパティまたは状態を更新したりできます。 コールバックが呼び出された場合、プロパティ システムは、有効なプロパティ値の変更があると判断しました。 コールバック メソッドは静的であるため、コールバックの d パラメーターは、クラスのどのインスタンスが変更を報告したかを示すので重要です。 一般的な実装では、イベント データの NewValue プロパティを使用し、その値を何らかの方法で処理します。通常は、 d として渡されたオブジェクトに対して他の変更を実行します。 プロパティの変更に対する追加の応答は、 NewValue によって報告された値を拒否するか、 OldValue を復元するか、 値を NewValue に適用されるプログラムによる制約に設定することです。

次の例では、 PropertyChangedCallback の実装を示します。 これは、PropertyMetadata の構築引数の一部として、前の Register の例で参照したメソッドを実装します。 このコールバックで対処されるシナリオは、クラスに "HasLabelValue" という名前の計算された読み取り専用プロパティも含まれているということです (実装は示されていません)。 "Label" プロパティが再評価されるたびに、このコールバック メソッドが呼び出され、コールバックによって依存する計算値が依存関係プロパティの変更と同期されたままになります。

private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    ImageWithLabelControl iwlc = d as ImageWithLabelControl; //null checks omitted
    String s = e.NewValue as String; //null checks omitted
    if (s == String.Empty)
    {
        iwlc.HasLabelValue = false;
    } else {
        iwlc.HasLabelValue = true;
    }
}
    Private Shared Sub OnLabelChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim iwlc As ImageWithLabelControl = CType(d, ImageWithLabelControl) ' null checks omitted
        Dim s As String = CType(e.NewValue,String) ' null checks omitted
        If s Is String.Empty Then
            iwlc.HasLabelValue = False
        Else
            iwlc.HasLabelValue = True
        End If
    End Sub
void ImageWithLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto iwlc{ d.as<ImageWithLabelControlApp::ImageWithLabelControl>() };
    auto s{ winrt::unbox_value<winrt::hstring>(e.NewValue()) };
    iwlc.HasLabelValue(s.size() != 0);
}
static void OnLabelChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    ImageWithLabelControl^ iwlc = (ImageWithLabelControl^)d;
    Platform::String^ s = (Platform::String^)(e->NewValue);
    if (s->IsEmpty()) {
        iwlc->HasLabelValue=false;
    }
}

構造体および列挙体におけるプロパティ変更時の動作

DependencyProperty の型が列挙型または構造体の場合、構造体または列挙値の内部値が変更されなかった場合でも、コールバックが呼び出される可能性があります。 これは、値が変更された場合にのみ呼び出される文字列などのシステム プリミティブとは異なります。 これは、内部的に行われるこれらの値に対するボックス操作とボックス化解除操作の副作用です。 値が列挙体または構造体であるプロパティ の PropertyChangedCallback メソッドがある場合は、値を自分でキャストし、現在キャストされた値で使用できるオーバーロードされた比較演算子を使用して 、OldValueNewValue を比較する必要があります。 または、そのような演算子が使用できない場合 (カスタム構造の場合など)、個々の値を比較する必要がある場合があります。 通常、結果が値が変更されていない場合は、何も行わないよう選択します。

private static void OnVisibilityValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    if ((Visibility)e.NewValue != (Visibility)e.OldValue)
    {
        //value really changed, invoke your changed logic here
    } // else this was invoked because of boxing, do nothing
}
Private Shared Sub OnVisibilityValueChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    If CType(e.NewValue,Visibility) != CType(e.OldValue,Visibility) Then
        '  value really changed, invoke your changed logic here
    End If
    '  else this was invoked because of boxing, do nothing
End Sub
static void OnVisibilityValueChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto oldVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.OldValue()) };
    auto newVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.NewValue()) };

    if (newVisibility != oldVisibility)
    {
        // The value really changed; invoke your property-changed logic here.
    }
    // Otherwise, OnVisibilityValueChanged was invoked because of boxing; do nothing.
}
static void OnVisibilityValueChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    if ((Visibility)e->NewValue != (Visibility)e->OldValue)
    {
        //value really changed, invoke your changed logic here
    }
    // else this was invoked because of boxing, do nothing
    }
}

ベスト プラクティス

カスタム依存関係プロパティを定義するときは、ベスト プラクティスとして次の考慮事項に留意してください。

DependencyObject とスレッド処理

すべての DependencyObject インスタンスは、Windows ランタイム アプリによって表示される現在の ウィンドウ に関連付けられている UI スレッドに作成する必要があります。 各 DependencyObject はメイン UI スレッドで作成する必要がありますが、 Dispatcher を呼び出すことによって、他のスレッドからのディスパッチャー参照を使用してオブジェクトにアクセスできます。

DependencyObject のスレッド処理の側面は、一般に、UI スレッドで実行されるコードのみが依存関係プロパティの値を変更または読み取ることができることを意味するためです。 通常、スレッドの問題は、 非同期 パターンとバックグラウンド ワーカー スレッドを正しく使用する一般的な UI コードで回避できます。 通常、DependencyObject 関連のスレッドの問題は、独自の DependencyObject 型を定義していて、DependencyObject が必ずしも適切ではないデータ ソースやその他のシナリオで使用しようとした場合にのみ発生します。

意図しないシングルトンの回避

参照型を受け取る依存関係プロパティを宣言し、 PropertyMetadata を確立するコードの一部としてその参照型のコンストラクターを呼び出すと、意図しないシングルトンが発生する可能性があります。 何が起こるかは、依存関係プロパティのすべての使用法が PropertyMetadata の 1 つのインスタンスのみを共有し、構築した単一の参照型を共有しようとするということです。 依存関係プロパティを使用して設定したその値型のサブプロパティは、意図していない方法で他のオブジェクトに伝達されます。

null 以外の値が必要な場合は、クラス コンストラクターを使用して参照型の依存関係プロパティの初期値を設定できますが、 これは依存関係プロパティの概要のためにローカル値と見なされることに注意してください。 クラスでテンプレートがサポートされている場合は、この目的でテンプレートを使用する方が適切な場合があります。 シングルトン パターンを回避し、便利な既定値を提供するもう 1 つの方法は、そのクラスの値に適した既定値を提供する参照型の静的プロパティを公開することです。

コレクション型の依存関係プロパティ

コレクション型の依存関係プロパティには、考慮すべき追加の実装の問題がいくつかあります。

コレクション型の依存関係プロパティは、Windows ランタイム API では比較的まれです。 ほとんどの場合、項目が DependencyObject サブクラスであるコレクションを使用できますが、コレクション プロパティ自体は従来の CLR または C++ プロパティとして実装されます。 これは、依存関係プロパティが関係する一般的なシナリオには、コレクションが必ずしも適していないためです。 例えば次が挙げられます。

  • 通常、コレクションはアニメーション化しません。
  • 通常、コレクション内の項目にはスタイルやテンプレートを事前設定しません。
  • コレクションへのバインドは主要なシナリオですが、コレクションをバインディング ソースにする依存関係プロパティである必要はありません。 バインド ターゲットの場合は、 ItemsControl または DataTemplate のサブクラスを使用してコレクション項目をサポートするか、ビュー モデル パターンを使用する方が一般的です。 コレクションとの間のバインドの詳細については、「 データ バインディングの詳細」を参照してください。
  • コレクションの変更に関する通知は、 INotifyPropertyChangedINotifyCollectionChanged などのインターフェイス、または ObservableCollection<T> からコレクション型を派生させることで、より適切に対処できます。

ただし、コレクション型の依存関係プロパティのシナリオは存在します。 次の 3 つのセクションでは、コレクション型の依存関係プロパティを実装する方法に関するいくつかのガイダンスを提供します。

コレクションの初期化

依存関係プロパティを作成する場合は、依存関係プロパティのメタデータを使用して既定値を設定できます。 ただし、既定値としてシングルトン静的コレクションを使用しないように注意してください。 代わりに、コレクション プロパティの所有者クラスのクラス コンストラクター ロジックの一部として、コレクション値を一意の (インスタンス) コレクションに意図的に設定する必要があります。

// WARNING - DO NOT DO THIS
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(new List<object>())
);

// DO THIS Instead
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);

public ImageWithLabelControl()
{
    // Need to initialize in constructor instead
    Items = new List<object>();
}

DependencyProperty とその PropertyMetadata の既定値は、DependencyProperty の静的定義の一部です。 既定のコレクション (またはその他のインスタンス化された) 値を既定値として指定すると、各クラスが通常は独自のコレクションを持つべきところ、クラスのすべてのインスタンスでその値が共有されます。

変更通知

依存関係プロパティとしてコレクションを定義しても、プロパティ システムが "PropertyChanged" コールバック メソッドを呼び出すことにより、コレクション内の項目の変更通知は自動的に提供されません。 コレクションまたはコレクション項目 (データ バインディング シナリオなど) の通知が必要な場合は、 INotifyPropertyChanged または INotifyCollectionChanged インターフェイスを実装します。 詳細については、「データ バインディングの詳細」を参照してください。

依存関係プロパティのセキュリティに関する考慮事項

依存関係プロパティをパブリック プロパティとして宣言します。 依存関係プロパティ識別子を public static readonly メンバーとして宣言します。 言語で許可されている他のアクセス レベル ( 保護など) を宣言しようとしても、依存関係プロパティは常に、プロパティ システム API と組み合わせて識別子を介してアクセスできます。 依存関係プロパティ識別子を内部またはプライベートとして宣言すると、プロパティ システムが正常に動作できないため、機能しません。

ラッパー プロパティは、便宜上、代わりに GetValue または SetValue を呼び出すことによって、ラッパーに適用されるセキュリティ メカニズムをバイパスできます。 したがって、ラッパー プロパティはパブリックのままにします。それ以外の場合は、正当な呼び出し元が実際のセキュリティ上の利点を提供せずに使用するのを難しくするだけです。

Windows ランタイムには、カスタム依存関係プロパティを読み取り専用として登録する方法はありません。

依存関係プロパティとクラス コンストラクター

クラス コンストラクターは仮想メソッドを呼び出してはならないという一般的な原則があります。 これは、コンストラクターを呼び出して派生クラス コンストラクターの基本初期化を実行でき、構築中のオブジェクト インスタンスがまだ完全に初期化されていない場合に、コンストラクターを介して仮想メソッドを入力する可能性があるためです。 DependencyObject から既に派生しているクラスから派生する場合は、プロパティ システム自体がサービスの一部として内部的に仮想メソッドを呼び出して公開することに注意してください。 実行時の初期化に関する潜在的な問題を回避するために、クラスのコンストラクター内で依存関係プロパティの値を設定しないでください。

C++/CX アプリの依存関係プロパティの登録

C++/CX でプロパティを登録する実装は、ヘッダーと実装ファイルへの分離と、実装ファイルのルート スコープでの初期化が不適切な方法であるため、C# よりも複雑です。 (Visual C++ コンポーネント拡張機能 (C++/CX) はルート スコープから静的初期化子コードを DllMain に直接配置しますが、C# コンパイラは静的初期化子をクラスに割り当てて 、DllMain の読み込みロックの問題を回避します。 ここでのベスト プラクティスは、クラスに対するすべての依存関係プロパティの登録を行うヘルパー関数 (クラスごとに 1 つの関数) を宣言することです。 アプリが使用するカスタムクラスごとに、それぞれのクラスで公開されているヘルパー登録関数を参照する必要があります。 する前に、App::App() (InitializeComponent) の一部として各ヘルパー登録関数を 1 回呼び出します。 このコンストラクターは、アプリが初めて実際に参照された場合にのみ実行されます。たとえば、中断されたアプリが再開された場合は、再度実行されません。 また、前の C++ 登録例で示したように、各 Register 呼び出しに関する nullptr チェックは重要です。関数の呼び出し元がプロパティを 2 回登録できないのは保険です。 2 回目の登録呼び出しでは、プロパティ名が重複するため、このようなチェックなしでアプリがクラッシュする可能性があります。 この実装パターンは、サンプルの C++/CX バージョンのコードを見ると、 XAML ユーザー コントロールとカスタム コントロールのサンプル で確認できます。