WPFのDependencyPropertyが登録済みかを安全に確認する方法|Dot.ColorPropertyで学ぶ実践リフレクション

WPF のカスタムコントロールやカスタムクラスを書いていると、「この型にこの DependencyProperty は本当に登録されているのか?」を安全に確認したくなる場面があります。昔のサンプルコードでは、リフレクションで全フィールドを走査して「型名が DependencyProperty かどうか」を文字列で判定する方法がよく紹介されていましたが、今の .NET / WPF でベストなやり方はどう整理できるでしょうか。本記事では、Dot クラスの ColorProperty を例に、実用的で型安全な確認手法をまとめます。

目次

WPF の DependencyProperty は「登録済みか」をどう見分ける?

WPF の依存プロパティ(DependencyProperty)は、通常次のようなパターンで定義します。

public class Dot : DependencyObject
{
    public static readonly DependencyProperty ColorProperty =
        DependencyProperty.Register(
            nameof(Color),
            typeof(Color),
            typeof(Dot),
            new PropertyMetadata(Colors.Black));

    public Color Color
    {
        get => (Color)GetValue(ColorProperty);
        set => SetValue(ColorProperty, value);
    }
}

ここでポイントになるのは次の 2 つです。

  • 静的フィールド名:ColorProperty
  • CLR プロパティ名:Color

質問として多いのは、例えばこんなケースです。

  • 「Dot 型に ColorProperty が登録されているか、実行時にチェックしたい」
  • 「ライブラリの外から、ある型が特定の DependencyProperty を持っているか確認したい」
  • 「AddOwner や添付プロパティも含めて、DependencyProperty の有無を調べたい」

昔のサンプルでは、

  • GetFields() で全フィールドを列挙
  • フィールドの型名が "DependencyProperty" かどうかで判定

といった方法がよく紹介されていました。しかし、この方法は

  • 文字列比較であり型安全でない
  • 全フィールド列挙のため無駄が多い(パフォーマンス面)

といった弱点があります。

現代的には、次のようなアプローチに整理しておくとスッキリします。

  • 方法A:静的フィールド(<プロパティ名>Property)を直接 GetField で取得して判定
  • 方法B:DependencyPropertyDescriptor.FromName で名前から検索して判定

方法A:静的フィールド ColorProperty を直接リフレクションで探す(推奨)

もっともシンプルで高速な方法は、命名規約に従って静的フィールドを直接探すやり方です。

  • 依存プロパティの静的フィールド名は {CLRプロパティ名}Property が慣例
  • 慣例どおりであれば GetField("ColorProperty") で一発特定できる
  • 型比較に typeof(DependencyProperty) を使えば型安全

Dot.ColorProperty を対象にした汎用メソッド

using System;
using System.Reflection;
using System.Windows; // DependencyProperty

public static class DependencyPropertyHelper
{
    // "Color" / "ColorProperty" のどちらを渡してもOK
    public static bool IsDependencyPropertyRegistered(Type ownerType, string propertyOrFieldName)
    {
        var fieldName = propertyOrFieldName.EndsWith("Property", StringComparison.Ordinal)
            ? propertyOrFieldName
            : propertyOrFieldName + "Property";

        var field = ownerType.GetField(
            fieldName,
            BindingFlags.Public
            | BindingFlags.NonPublic
            | BindingFlags.Static
            | BindingFlags.FlattenHierarchy);

        return field?.FieldType == typeof(DependencyProperty);
    }

    // DependencyProperty の実体も必要な場合
    public static DependencyProperty? GetDependencyProperty(Type ownerType, string propertyOrFieldName)
    {
        var fieldName = propertyOrFieldName.EndsWith("Property", StringComparison.Ordinal)
            ? propertyOrFieldName
            : propertyOrFieldName + "Property";

        var field = ownerType.GetField(
            fieldName,
            BindingFlags.Public
            | BindingFlags.NonPublic
            | BindingFlags.Static
            | BindingFlags.FlattenHierarchy);

        return field?.FieldType == typeof(DependencyProperty)
            ? (DependencyProperty?)field.GetValue(null)
            : null;
    }
}

Dot クラスの ColorProperty に対する利用例

// ・Dot クラスに ColorProperty が定義されているか
bool byFieldName = DependencyPropertyHelper.IsDependencyPropertyRegistered(
    typeof(Dot), "ColorProperty");

// ・CLRプロパティ名から判定したい場合(内部で "ColorProperty" に変換)
bool byPropertyName = DependencyPropertyHelper.IsDependencyPropertyRegistered(
    typeof(Dot), "Color");

// ・DependencyProperty の実体を取得したい場合
DependencyProperty? dp = DependencyPropertyHelper.GetDependencyProperty(
    typeof(Dot), "Color");

この方法であれば、「特定のフィールド名を持つ DependencyProperty が宣言されているか」を最小限のコストでチェックできます。

BindingFlags の意味を整理

上のコードで指定している BindingFlags は、意図を理解しておくと流用しやすくなります。

フラグ意味なぜ必要か
Public公開フィールドを対象にする通常の DependencyProperty は public static readonly で宣言されるため
NonPublic非公開フィールドも対象にするライブラリ内で internal / private にしている場合も拾えるようにするため
Static静的フィールドを対象にするDependencyProperty フィールドは必ず static で宣言されるため
FlattenHierarchy基底クラスの static フィールドも検索継承したカスタムコントロールなどで基底型の DP を見るケースに対応するため

古いサンプルでは GetFields() で全フィールド列挙後に field.FieldType.Name == "DependencyProperty" のような判定が見られますが、次の点で非推奨です。

  • 不要なフィールドまで列挙するため遅い
  • 型名を文字列で比較しており typeof(DependencyProperty) を使うより壊れやすい

そのため、フィールド名が分かっている場合は、GetField で直接取りに行く方法を基本とするのがおすすめです。

方法B:DependencyPropertyDescriptor.FromName で名前から検索する

一方で、次のようなケースでは「フィールド検索だけ」では判定できないことがあります。

  • ライブラリ作者があえて DependencyProperty のフィールドを公開していない
  • AddOwner を使って別の型に所有権を追加したが、新しい型にはフィールドを持たせていない
  • 添付プロパティ(Attached Property)で、どの型に対して有効か動的に調べたい

こうしたケースでは、DependencyPropertyDescriptor.FromName を使うのが有効です。

using System;
using System.ComponentModel; // DependencyPropertyDescriptor
using System.Windows;

public static class DependencyPropertyNameHelper
{
    // ownerType: 依存プロパティを宣言した型(所有者)
    // targetType: その依存プロパティを適用する対象型(通常は ownerType と同じ)
    public static bool IsDpRegisteredByName(
        Type ownerType,
        string propertyName,
        Type? targetType = null)
    {
        targetType ??= ownerType;

        var descriptor = DependencyPropertyDescriptor.FromName(
            propertyName,  // "ColorProperty" ではなく "Color"
            ownerType,
            targetType);

        return descriptor != null;
    }
}

Dot.Color を FromName で判定する

bool exists = DependencyPropertyNameHelper.IsDpRegisteredByName(
    typeof(Dot),
    "Color",      // CLR プロパティ名
    typeof(Dot)); // Dot に対して有効か?

FromName に渡すのはあくまで CLR プロパティ名(”Color”) であり、フィールド名(”ColorProperty”)ではないことに注意が必要です。

ownerType と targetType のイメージ

ケースownerTypetargetTypeコメント
通常の依存プロパティtypeof(Dot)typeof(Dot)Dot が Color を所有し、Dot に対して適用される
カスタムコントロールの DPtypeof(MyControl)typeof(MyControl)基本は ownerType と targetType を同じにする
添付プロパティ定義しているクラス(例:Grid)値を添付する型(例:DependencyObject)広く調べたいときは typeof(DependencyObject) を指定

AddOwner で別の型に所有権を追加する場合などは、ownerType / targetType の組み合わせを意識して指定すると、より正確な判定が可能になります。

Dot.ColorProperty を例にした具体的な実装手順

ここからは、Dot クラスと ColorProperty を実例に、DependencyProperty の登録確認をどのように組み込めるかを具体的に見ていきます。

サンプルの Dot クラス

using System.Windows;
using System.Windows.Media;

public class Dot : DependencyObject
{
    public static readonly DependencyProperty ColorProperty =
        DependencyProperty.Register(
            nameof(Color),
            typeof(Color),
            typeof(Dot),
            new PropertyMetadata(Colors.Black));

    public Color Color
    {
        get =&gt; (Color)GetValue(ColorProperty);
        set =&gt; SetValue(ColorProperty, value);
    }
}

起動時に DependencyProperty の存在を検証するコード例

public static class StartupCheck
{
    public static void VerifyDependencyProperties()
    {
        // 方法A:フィールド名から直接判定(最速)
        bool colorFieldExists =
            DependencyPropertyHelper.IsDependencyPropertyRegistered(typeof(Dot), "ColorProperty");

        // 方法B:CLR プロパティ名から FromName で判定(AddOwner 等にも強い)
        bool colorNameExists =
            DependencyPropertyNameHelper.IsDpRegisteredByName(typeof(Dot), "Color", typeof(Dot));

        if (!colorFieldExists || !colorNameExists)
        {
            // ログ出力や例外など、好みに応じて扱う
            throw new InvalidOperationException("Dot.Color の DependencyProperty が期待どおりに登録されていません。");
        }
    }
}

このように、起動時(アプリケーションのエントリポイントや DI コンテナ初期化時など)にチェックを入れておくことで、

  • プロパティ名のリファクタリングミス
  • ライブラリのバージョン違いによる仕様変更

などに対して、早い段階で気付きやすくなります。

方法A(フィールド探索)と方法B(FromName)の比較

ここで、2 つの方法を整理して比較してみます。

項目方法A:GetField で静的フィールド検索方法B:DependencyPropertyDescriptor.FromName
指定する名前フィールド名(例:"ColorProperty"、ヘルパーで "Color" から変換も可)CLR プロパティ名(例:"Color")
検索単位指定型の静的フィールドWPF のプロパティシステム全体(メタデータ)
パフォーマンス非常に高速(フィールド 1 件への直接アクセス)やや重め(メタデータ検索とラッパー生成)
AddOwner への対応新しい型にフィールドが無ければ検出不可ownerType と targetType を適切に指定すれば検出可能
添付プロパティ静的フィールドがあれば検出可能targetType に幅広い型を指定することで柔軟に判定可能
実装の簡単さDP の命名規約に依存するが実装はシンプルownerType / targetType の区別を理解する必要あり
おすすめの使いどころ自分のコード/社内コードで命名規約が守られている前提のチェック外部ライブラリや AddOwner、添付プロパティを含めた包括的な確認

実務的には、

  • 自分でコントロールを実装しているプロジェクトでは方法Aを基本
  • 外部ライブラリや AddOwner を多用するフレームワーク的コードでは方法Bも併用

という使い分けがしっくり来ることが多いです。

AddOwner・OverrideMetadata と DependencyProperty 判定

WPF では、ある型の DependencyProperty を別の型でも使えるようにする仕組みとして AddOwner が用意されています。

public class DotChild : Dot
{
    // フィールドを持たずに AddOwner だけするパターン
    static DotChild()
    {
        // Dot.ColorProperty を DotChild でも使えるようにする
        ColorProperty.AddOwner(typeof(DotChild));
    }
}

このような場合、DotChild 側に public static readonly DependencyProperty ColorProperty; を宣言しないこともあります。

  • 方法A(フィールド探索):DotChild には ColorProperty フィールドが無いので検出できない
  • 方法B(FromName):ownerType と targetType を工夫すれば検出可能

例えば、次のような判定が考えられます。

// Dot に宣言された Color を、DotChild に対して使えるかどうか
bool existsOnChild = DependencyPropertyNameHelper.IsDpRegisteredByName(
    typeof(Dot),
    "Color",
    typeof(DotChild));

OverrideMetadata を用いてメタデータだけ上書きしている場合も含め、

  • 「誰が宣言(所有)しているか」= ownerType
  • 「どの型に適用したいか」= targetType

という整理で考えれば、FromName による判定ロジックを組み立てやすくなります。

添付プロパティ(Attached Property)の登録確認

添付プロパティは、XAML では Grid.Row="1" のように記述されるプロパティで、

  • 所有者:Grid クラス
  • 設定先:任意の DependencyObject(ボタンやテキストボックスなど)

という関係になります。添付プロパティの登録確認も、方法A / 方法B で同じように整理できます。

添付プロパティの典型的な宣言例

public static class MyAttachedProperties
{
    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached(
            "IsEnabled",
            typeof(bool),
            typeof(MyAttachedProperties),
            new PropertyMetadata(false));

    public static void SetIsEnabled(DependencyObject obj, bool value)
        =&gt; obj.SetValue(IsEnabledProperty, value);

    public static bool GetIsEnabled(DependencyObject obj)
        =&gt; (bool)obj.GetValue(IsEnabledProperty);
}

添付プロパティの登録確認例

// 方法A:フィールドを直接見る
bool fieldExists = DependencyPropertyHelper.IsDependencyPropertyRegistered(
    typeof(MyAttachedProperties),
    "IsEnabledProperty");

// 方法B:FromName で広く調べる
bool attachedExists = DependencyPropertyNameHelper.IsDpRegisteredByName(
    typeof(MyAttachedProperties),
    "IsEnabled",
    typeof(DependencyObject)); // どの DependencyObject にも添付可能か?

添付プロパティを多用するカスタムフレームワークを作っている場合、FromName を使ったパターンを知っておくと「どのコントロールにどの添付プロパティが有効か」を動的に調査する仕組みを作りやすくなります。

実務での活用シナリオ

「DependencyProperty が登録済みかどうか」を判定する仕組みは、次のような場面で役立ちます。

カスタムコントロールの自動検証

  • カスタムコントロールの開発時に、「プロパティ名と DependencyProperty フィールド名が一致しているか」をテストコードで検証
  • 命名規約(Color ↔ ColorProperty)の崩れを早期に検知
public class DependencyPropertyConventionTests
{
    public void Dot_Color_DP_Should_Exist()
    {
        // CLR プロパティ名と DP フィールドの両方をチェック
        bool dpExists = DependencyPropertyHelper.IsDependencyPropertyRegistered(typeof(Dot), "Color");
        bool nameExists = DependencyPropertyNameHelper.IsDpRegisteredByName(typeof(Dot), "Color");

        if (!dpExists || !nameExists)
        {
            throw new Exception("Dot.Color の依存プロパティ定義が不正です。");
        }
    }
}

動的テーマ/スタイルエンジンでの利用

  • 文字列で指定されたプロパティ名から、DependencyProperty を動的に引き当ててスタイルを適用
  • 存在しないプロパティ名が指定された場合は警告ログを出力
public static class DynamicStyleEngine
{
    public static bool TrySetDpValue(
        DependencyObject target,
        string propertyName,
        object? value)
    {
        var type = target.GetType();

        // まずフィールドから DP を探す(高速)
        var dp = DependencyPropertyHelper.GetDependencyProperty(type, propertyName);

        // 無ければ FromName にフォールバック
        if (dp == null)
        {
            var descriptor = DependencyPropertyDescriptor.FromName(
                propertyName,
                type,
                type);
            dp = descriptor?.DependencyProperty;
        }

        if (dp == null)
        {
            // ログなど
            return false;
        }

        target.SetValue(dp, value);
        return true;
    }
}

このように、まず方法A(フィールド)で高速に探し、見つからなければ方法B(FromName)にフォールバックする構成にしておくと、

  • 通常のケースでは高速に動作
  • AddOwner や添付プロパティなど複雑なケースでも対応可能

というバランスの良い実装になります。

パフォーマンスとキャッシュ戦略

リフレクションや DependencyPropertyDescriptor の呼び出しは、通常のプロパティアクセスに比べるとコストが高めです。とはいえ、

  • アプリケーション起動時に一度だけチェックする
  • 開発時のテストコードでのみ使用する

といった用途であれば、大きな問題になることはほとんどありません。

一方、テーマエンジンや画面定義エンジンなどで「大量のオブジェクトに対して繰り返し判定する」ような場合には、次のようなキャッシュ戦略を取ると安心です。

public static class DpCache
{
    private static readonly Dictionary&lt;(Type owner, string name), DependencyProperty?&gt; _cache
        = new();

    public static DependencyProperty? GetCachedDp(Type ownerType, string propertyName)
    {
        var key = (ownerType, propertyName);

        if (_cache.TryGetValue(key, out var cached))
        {
            return cached;
        }

        // まずフィールドから
        var dp = DependencyPropertyHelper.GetDependencyProperty(ownerType, propertyName);

        // フィールドに無ければ FromName
        if (dp == null)
        {
            var descriptor = DependencyPropertyDescriptor.FromName(
                propertyName,
                ownerType,
                ownerType);
            dp = descriptor?.DependencyProperty;
        }

        _cache[key] = dp;
        return dp;
    }
}

キャッシュを導入しておくことで、同じ型・同じプロパティ名に対する判定を何度も繰り返す場合でもパフォーマンス劣化を抑えられます。

まとめ:古いサンプルを「今風」に書き直すコツ

最後に、本記事のポイントを DependencyProperty 登録確認の観点から整理します。

  • 昔からよくある「リフレクションで DependencyProperty を探す」方針自体は、今でも有効
  • ただし、GetFields() で全列挙して文字列で型名比較するスタイルは、
    • 遅い
    • 型安全でない
    ため、可能であれば避ける
  • 方法A:GetField + typeof(DependencyProperty)
    • フィールド名(ColorProperty)を直接指定して高速に判定
    • 自分のコードベースで命名規約が守られている前提では、まずこれを採用する
  • 方法B:DependencyPropertyDescriptor.FromName
    • CLR プロパティ名(Color)からメタデータ検索
    • フィールドを公開していないライブラリや AddOwner、添付プロパティにも対応しやすい
  • 両者を組み合わせて
    • まずフィールドで探し、見つからなければ FromName にフォールバック
    • 必要に応じて結果をキャッシュ
    することで、速度と柔軟性を両立できる
  • 設計面では、
    • 標準パターン(public static readonly DependencyProperty XxxProperty) に揃える
    • CLR プロパティ名と DP フィールド名の対応をテストで検証しておく

WPF の DependencyProperty は強力な反面、リフレクションを絡めたコードはどうしても複雑に見えがちです。しかし、

  • 「フィールド名から直接探す」
  • 「名前から FromName で探す」

という 2 つの軸に整理しておけば、古いサンプルコードも「今風の、安全で読みやすい形」に書き直すことができます。Dot.ColorProperty のような素朴な例から始めて、自分のプロジェクトに合ったパターンを整えていくと、WPF コードの保守性と信頼性を一段引き上げられます。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次