有什么方法可以注销WPF依赖项属性?

时间:2020-03-06 14:51:10  来源:igfitidea点击:

我在单元测试中遇到一个不寻常的问题。我正在测试的类在运行时动态创建一个依赖项属性,该依赖项属性的类型可以根据情况而有所不同。在编写单元测试时,我需要创建具有不同类型的依赖项属性,这会导致错误,因为我们无法重新定义现有的依赖项属性。

那么,有什么方法可以取消注册依赖项属性或者更改现有依赖项属性的类型?

谢谢!

OverrideMetadata()仅允许我们更改一些东西,例如默认值,因此它没有帮助。 AppDomain方法是一个好主意,可能会起作用,但似乎比为了单元测试而真正想要研究的要复杂得多。

我从来没有找到一种方法来注销依赖项属性,所以我仔细检查并仔细地重新组织了单元测试,以避免出现此问题。我的测试覆盖面要少一些,但是由于这个问题在真正的应用程序中永远不会发生,只有在单元测试期间我才能忍受。

谢谢帮助!

解决方案

我不认为我们可以取消注册依赖项属性,但是可以通过重写元数据来重新定义它,如下所示:

MyDependencyProperty.OverrideMetadata(typeof(MyNewType), 
                     new PropertyMetadata());

如果其他所有操作均失败,则可以为每个测试创建一个新的AppDomain。

如果我们为这样的标签注册名称:

Label myLabel = new Label();
this.RegisterName(myLabel.Name, myLabel);

我们可以使用以下方法轻松注销该名称:

this.UnregisterName(myLabel.Name);

就在昨天,当尝试测试自己的DependencyProperty创建类时,我遇到了类似的问题。我遇到了这个问题,并注意到没有真正的解决方案来注销依赖项属性。因此,我使用Red Gate .NET Reflector进行了一些挖掘,以了解我能想到的结果。

查看DependencyProperty.Register重载,它们似乎都指向DependencyProperty.RegisterCommon。该方法包括两个部分:

首先检查财产是否已经注册

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;
}

这两部分都围绕HashTableDependencyProperty.PropertyFromName展开。我还注意到DependencyProperty.RegisteredPropertyList,ItemStructList <DependencyProperty>,但是还没有看到它的使用位置。但是,为了安全起见,我认为如果可能的话,我也会尝试从中删除。

因此,我总结了以下允许我"取消注册"依赖项属性的代码。

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);
}

它足以让我运行我的测试,而没有遇到" AlreadyRegistered"异常。但是,我强烈建议我们不要在任何生产代码中使用此代码。 MSFT可能选择了没有正式的方法来注销依赖项属性的原因很可能,而试图违背它只是在自找麻烦。

我面对的场景是我创建了一个自定义控件,该控件继承自Selector,该控件具有两个ItemsSource属性,即Horizo​​ntalItemsSource和VerticalItemsSource。

我什至不使用ItemsControl属性,也不希望用户能够访问它。

因此,我读了Statenjason的一个很好的答案,它给了我关于如何删除DP的巨大观点。
但是,我的问题是,因为我将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的子类确实提供了ItemsSourcedp。