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 のイメージ
| ケース | ownerType | targetType | コメント |
|---|---|---|---|
| 通常の依存プロパティ | typeof(Dot) | typeof(Dot) | Dot が Color を所有し、Dot に対して適用される |
| カスタムコントロールの DP | typeof(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 => (Color)GetValue(ColorProperty);
set => 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)
=> obj.SetValue(IsEnabledProperty, value);
public static bool GetIsEnabled(DependencyObject obj)
=> (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<(Type owner, string name), DependencyProperty?> _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、添付プロパティにも対応しやすい
- CLR プロパティ名(
- 両者を組み合わせて
- まずフィールドで探し、見つからなければ FromName にフォールバック
- 必要に応じて結果をキャッシュ
- 設計面では、
- 標準パターン(
public static readonly DependencyProperty XxxProperty) に揃える - CLR プロパティ名と DP フィールド名の対応をテストで検証しておく
- 標準パターン(
WPF の DependencyProperty は強力な反面、リフレクションを絡めたコードはどうしても複雑に見えがちです。しかし、
- 「フィールド名から直接探す」
- 「名前から FromName で探す」
という 2 つの軸に整理しておけば、古いサンプルコードも「今風の、安全で読みやすい形」に書き直すことができます。Dot.ColorProperty のような素朴な例から始めて、自分のプロジェクトに合ったパターンを整えていくと、WPF コードの保守性と信頼性を一段引き上げられます。

コメント