WPF依存関係プロパティを登録解除する方法はありますか?
-
02-07-2019 - |
質問
ユニットテストで異常な問題が発生しています。私がテストしているクラスは、実行時に依存関係プロパティを動的に作成し、その依存関係プロパティのタイプは状況に応じて変化します。単体テストの作成中に、異なるタイプの依存関係プロパティを作成する必要があります。既存の依存関係プロパティを再定義できないため、エラーが発生します。
では、依存関係プロパティの登録を解除する方法、または既存の依存関係プロパティのタイプを変更する方法はありますか?
ありがとう!
OverrideMetadata()を使用すると、デフォルト値などごくわずかな変更しかできないため、役に立たない。 AppDomainアプローチは優れたアイデアであり、動作する可能性がありますが、単体テストのために私が実際に掘り下げたいと思ったよりも複雑に思えます。
依存関係プロパティの登録を解除する方法を見つけられなかったので、問題を回避するためにユニットテストをパントし、慎重に再編成しました。テストカバレッジは少し少なくなっていますが、この問題は実際のアプリケーションでは発生せず、単体テスト中にのみ発生します。
助けてくれてありがとう!
解決
昨日、クラスを作成するDependencyPropertyをテストしようとしたときに、同様の問題が発生しました。私はこの質問に出くわし、依存関係プロパティの登録を解除する実際の解決策がないことに気付きました。そこで、 Red Gate .NET Reflector を使って掘り下げて、何ができるかを調べました。
DependencyProperty.Register
オーバーロードを見ると、それらはすべてDependencyProperty.RegisterCommon
を指しているように見えます。このメソッドには2つの部分があります:
最初に、プロパティがすでに登録されているかどうかを確認します
FromNameKey key = new FromNameKey(name, ownerType);
lock (Synchronized)
{
if (PropertyFromName.Contains(key))
{
throw new ArgumentException(SR.Get("PropertyAlreadyRegistered",
new object[] { name, ownerType.Name }));
}
}
第二に、DependencyPropertyの登録
DependencyProperty dp =
new DependencyProperty(name, propertyType, ownerType,
defaultMetadata, validateValueCallback);
defaultMetadata.Seal(dp, null);
//...Yada yada...
lock (Synchronized)
{
PropertyFromName[key] = dp;
}
両方のピースは、HashTableであるDependencyProperty.PropertyFromName
を中心にしています。 DependencyProperty.RegisteredPropertyList
、ItemStructList<DependencyProperty>
にも気づきましたが、どこで使用されているかはわかりません。ただし、安全のために、可能であればそれからも削除しようと考えました。
だから、<!> quot; unregister <!> quot;を許可する次のコードを作成しました。依存関係プロパティ。
private void RemoveDependency(DependencyProperty prop)
{
var registeredPropertyField = typeof(DependencyProperty).
GetField("RegisteredPropertyList", BindingFlags.NonPublic | BindingFlags.Static);
object list = registeredPropertyField.GetValue(null);
var genericMeth = list.GetType().GetMethod("Remove");
try
{
genericMeth.Invoke(list, new[] { prop });
}
catch (TargetInvocationException)
{
Console.WriteLine("Does not exist in list");
}
var propertyFromNameField = typeof(DependencyProperty).
GetField("PropertyFromName", BindingFlags.NonPublic | BindingFlags.Static);
var propertyFromName = (Hashtable)propertyFromNameField.GetValue(null);
object keyToRemove = null;
foreach (DictionaryEntry item in propertyFromName)
{
if (item.Value == prop)
keyToRemove = item.Key;
}
if (keyToRemove != null)
propertyFromName.Remove(keyToRemove);
}
<!> quot; AlreadyRegistered <!> quot;を取得せずにテストを実行するのに十分に機能しました。例外。ただし、いかなる種類の本番コードでもこれを使用しないことを強くお勧めします。 MSFTが依存関係プロパティの登録を解除する正式な方法を持たないことを選択し、単にトラブルを求めているだけです。
他のヒント
他のすべてが失敗した場合は、テストごとに新しいAppDomainを作成できます。
依存関係プロパティの登録を解除することはできないと思いますが、次のようにメタデータをオーバーライドすることで再定義できます:
MyDependencyProperty.OverrideMetadata(typeof(MyNewType),
new PropertyMetadata());
このようなラベルの名前を登録する場合:
Label myLabel = new Label();
this.RegisterName(myLabel.Name, myLabel);
次を使用して、名前を簡単に登録解除できます:
this.UnregisterName(myLabel.Name);
Selector
とHorizontalItemsSource
の2つのItemsSourceプロパティを持つことを意図したVerticalItemsSource
から継承するカスタムコントロールを作成するシナリオに直面していました。
ItemsControlプロパティも使用せず、ユーザーがアクセスできないようにします。
だから私は statenjasonの素晴らしい答え、そしてそれは私にDPを削除する方法に関する巨大なPOVを与えました。
ただし、私の問題は、ItemsSourceProperty
メンバーとItemsSource
をPrivate Shadows
(C#ではprivate new
)として宣言したため、MyControlType.ItemsSourceProperty
を使用すると参照するため、設計時にロードできなかったことです。影付き変数。
また、上記の回答(foreach DictionaryEntry
など)で説明したループを使用すると、反復中にコレクションが変更されたという例外がスローされました。
そのため、実行時にDependencyPropertyがハードコーディングされて参照され、コレクションが配列にコピーされるため、変更されない(VB.NET、申し訳ありませんが)、わずかに異なるアプローチを思いつきました:
Dim dpType = GetType(DependencyProperty)
Dim bFlags = BindingFlags.NonPublic Or BindingFlags.Static
Dim FromName =
Function(name As String, ownerType As Type) DirectCast(dpType.GetMethod("FromName",
bFlags).Invoke(Nothing, {name, ownerType}), DependencyProperty)
Dim PropertyFromName = DirectCast(dpType.GetField("PropertyFromName", bFlags).
GetValue(Nothing), Hashtable)
Dim dp = FromName.Invoke("ItemsSource", GetType(DimensionalGrid))
Dim entries(PropertyFromName.Count - 1) As DictionaryEntry
PropertyFromName.CopyTo(entries, 0)
Dim entry = entries.Single(Function(e) e.Value Is dp)
PropertyFromName.Remove(entry.Key)
重要な注意:上記のコードはすべてカスタムコントロールの共有コンストラクターで囲まれています。登録されているかどうかを確認する必要はありません。Selcetorのサブクラス<=> dpを提供します。
異なるDatatemplatesのContentPresenterに問題があり、そのうちの1つにPropertyChangedCallbackを持つDependencyPropertyがありました ContentPresentersコンテンツを別のDataTemplateに変更すると、コールバックが残りました。
UserControls Unloadedイベントで次を呼び出しました:
BindingOperations.ClearAllBindings(this);
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, new DispatcherOperationCallback(delegate { return null; }), null);
それは私のために働いた