Есть ли способ отменить регистрацию свойства зависимостей WPF?

StackOverflow https://stackoverflow.com/questions/146794

  •  02-07-2019
  •  | 
  •  

Вопрос

Я столкнулся с необычной проблемой во время модульных тестов.Класс, который я тестирую, динамически создает свойство зависимости во время выполнения, и тип этого свойства зависимости может меняться в зависимости от обстоятельств.При написании модульных тестов мне нужно создать свойство зависимостей разных типов, и это приводит к ошибкам, поскольку вы не можете переопределить существующее свойство зависимостей.

Итак, есть ли способ отменить регистрацию свойства зависимостей или изменить тип существующего свойства зависимостей?

Спасибо!


OverrideMetadata() позволяет изменить лишь некоторые вещи, например значение по умолчанию, поэтому это бесполезно.Подход AppDomain — хорошая идея, и он может работать, но кажется более сложным, чем мне хотелось бы вникать ради модульного тестирования.

Я так и не нашел способа отменить регистрацию свойства зависимостей, поэтому я тщательно реорганизовал свои модульные тесты, чтобы избежать этой проблемы.Я получаю немного меньше тестового покрытия, но поскольку эта проблема никогда не возникнет в реальном приложении и только во время модульного тестирования, я могу с этим смириться.

Спасибо за помощь!

Это было полезно?

Решение

Буквально вчера у меня возникла аналогичная проблема, когда я пытался протестировать свой собственный класс создания DependencyProperty.Я столкнулся с этим вопросом и заметил, что реального решения по отмене регистрации свойств зависимостей не существует.Поэтому я немного покопался, используя Отражатель Red Gate .NET чтобы посмотреть, что я могу придумать.

Глядя на 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;
}

Обе части сосредоточены вокруг DependencyProperty.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 решила не использовать формальный способ отмены регистрации свойства зависимостей, и попытка пойти против него просто напрашивается на неприятности.

Другие советы

Если все остальное не помогло, вы можете создать новый AppDomain для каждого теста.

Я не думаю, что вы можете отменить регистрацию свойства зависимости, но вы можете переопределить его, переопределив метаданные следующим образом:

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

Если мы зарегистрируем имя для метки следующим образом:

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

Мы можем легко отменить регистрацию имени, используя:

this.UnregisterName(myLabel.Name);

Я столкнулся со сценарием, в котором я создал собственный элемент управления, который наследуется от Selector который должен иметь два свойства ItemsSource, HorizontalItemsSource и VerticalItemsSource.

Я даже не использую свойство ItemsControl и не хочу, чтобы пользователь имел к нему доступ.

Итак, я прочитал отличный ответ Стейтенджейсона, и это дало мне огромное представление о том, как удалить DP.
Однако моя проблема заключалась в том, что, поскольку я объявил ItemsSourceProperty член и ItemsSource как Private Shadows (private new в C#), я не мог загрузить его во время разработки, так как использовал 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 ДЕЙСТВИТЕЛЬНО обеспечивает это ItemsSource дп.

У меня была проблема с ContentPresenter с различными данными, когда у одного из них была зависимость пропертизации с PropertyChangEdCallback при изменении контента Content -Presenters на другой, чтобы получить обратный вызов, остался.

В событии UserControls Unloaded я вызвал:

BindingOperations.ClearAllBindings(this);
Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Normal, new DispatcherOperationCallback(delegate { return null; }), null);

Это сработало для меня

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top