Ответ 1
Ваш вопрос действительно интересный, но его объем действительно очень большой.
В этой ситуации действительно полезным инструментом является ILSpy, что позволяет вам взглянуть на реализацию фреймворка.
Одна вещь, с которой я столкнулся бы, это следующее утверждение:
Ответ, который я получил, заключается в том, что механизм привязки данных в С# в сочетании с интерфейсом WPF/Windows Forms UI
Я не согласен; механизм привязки данных тесно связан с реализацией событий .Net, но Target и Source могут быть любыми - большинство примеров будут Windows Forms, WPF или ASP.Net, потому что они являются наиболее распространенными интерфейсами для .Net-языков, но это вполне возможно использовать множественную привязку в других сценариях без пользовательского интерфейса.
Что произойдет, если вы добавите двустороннюю привязку? Ну, если мы посмотрим на источник MultiBinding, отметим несколько интересных вещей:
- Он предоставляет свойство BindingMode, которое описывает сценарий привязки - обычно либо
OneWay
, либоTwoWay
- В нем представлены два интересных события:
NotifyOnSourceUpdated
иNotifyOnTargetUpdated
Какие имеют базовую форму:
// System.Windows.Data.MultiBinding
/// <summary>Gets or sets a value that indicates whether to raise the <see cref="E:System.Windows.FrameworkElement.SourceUpdated" /> event when a value is transferred from the binding target to the binding source.</summary>
/// <returns>true if the <see cref="E:System.Windows.FrameworkElement.SourceUpdated" /> event will be raised when the binding source value is updated; otherwise, false. The default value is false.</returns>
[DefaultValue(false)]
public bool NotifyOnSourceUpdated
{
get
{
return base.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated);
}
set
{
bool flag = base.TestFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated);
if (flag != value)
{
base.CheckSealed();
base.ChangeFlag(BindingBase.BindingFlags.NotifyOnSourceUpdated, value);
}
}
}
то есть. мы используем события, чтобы сообщить нам, когда источник обновлен (OneWay
), и когда цель также обновлена (для привязки TwoWay
)
Обратите внимание, что существует также класс PriorityBinding
, который работает аналогичным образом, за исключением того, что вы можете подписаться на несколько источников данных, и он будет расставлять приоритеты для тех, кто быстро возвращает данные.
Итак, форма того, как это работает, понятна - когда мы создаем привязку, мы подписываемся на изменения с одной стороны (для обновлений только для чтения) или с обеих сторон (когда данные могут быть изменены в GUI, например, и отправляется обратно в источник данных), причем все уведомления управляются посредством событий.
Следующий вопрос, действительно, кто управляет событиями? Простой ответ заключается в том, что и Target, и Source делают. То, почему важно реализовать INotifyPropertyChanged
, например, - все, что действительно делают Bindings, - это создать контракт о том, как обе стороны должны подписываться друг на друга, - это контракт, с которым Target и Source тесно связаны.
ObservableCollection является интересным тестовым примером для изучения, поскольку он широко используется в приложениях GUI для продвижения обновлений в источнике данных в пользовательском интерфейсе и для отправка изменений данных в пользовательском интерфейсе обратно в исходный источник данных.
Обратите внимание (смотря на код), как фактическое событие для обмена информацией о вещах изменилось очень просто, НО код для управления надстройками, удалениями, обновлениями на самом деле очень зависит от согласованности через свойство SimpleMonitor (BlockReentrancy
и CheckReentrancy
) - это эффективно гарантирует, что операции являются атомарными и что подписчики уведомляются об изменениях в порядке их возникновения и что базовая коллекция соответствует обновленным.
Это действительно сложная часть всей операции.
Короче говоря, реализация DataBinding в .Net не тесно связана с технологиями GUI; это просто то, что в большинстве примеров будет представлен DataBinding в контексте приложений Windows Forms, WPF или ASP.Net. Фактическое привязывание данных управляется событиями, и для того, чтобы вы могли использовать его, более важно синхронизировать и управлять изменениями в ваших данных. Структура DataBinding позволит вам одновременно связывать Target и Source вместе с общими обновлениями данных через контракт ( Интерфейсы), который он определяет.
Удачи, -)
EDIT:
Я сел и создал два класса MyCharacter
и MyCharacterAttribute
с явной целью настройки привязки данных TwoWay между атрибутами Health
и HealthValue
:
public class MyCharacter : DependencyObject
{
public static DependencyProperty HealthDependency =
DependencyProperty.Register("Health",
typeof(Double),
typeof(MyCharacter),
new PropertyMetadata(100.0, HealthDependencyChanged));
private static void HealthDependencyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
}
public double Health
{
get
{
return (double)GetValue(HealthDependency);
}
set
{
SetValue(HealthDependency, value);
}
}
public void DrinkHealthPotion(double healthRestored)
{
Health += healthRestored;
}
}
public class MyCharacterAttributes : DependencyObject
{
public static DependencyProperty HealthDependency =
DependencyProperty.Register("HealthValue",
typeof(Double),
typeof(MyCharacterAttributes),
new PropertyMetadata(100.0, HealthAttributeDependencyChanged));
public double HealthValue
{
get
{
return (Double)GetValue(HealthDependency);
}
set
{
SetValue(HealthDependency, value);
}
}
public List<BindingExpressionBase> Bindings { get; set; }
public MyCharacterAttributes()
{
Bindings = new List<BindingExpressionBase>();
}
private static void HealthAttributeDependencyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
}
}
Наиболее важные вещи, которые следует отметить здесь, - это наследование от DependencyObject и реализация DependencyProperty.
На практике тогда происходит следующее. Я создал простую форму WPF и установил следующий код:
MyCharacter Character { get; set; }
MyCharacterAttributes CharacterAttributes = new MyCharacterAttributes();
public MainWindow()
{
InitializeComponent();
Character = new MyCharacter();
CharacterAttributes = new MyCharacterAttributes();
// Set up the data binding to point at Character (Source) and
// Property Health (via the constructor argument for Binding)
var characterHealthBinding = new Binding("Health");
characterHealthBinding.Source = Character;
characterHealthBinding.NotifyOnSourceUpdated = true;
characterHealthBinding.NotifyOnTargetUpdated = true;
characterHealthBinding.Mode = BindingMode.TwoWay;
characterHealthBinding.IsAsync = true;
// Now we bind any changes to CharacterAttributes, HealthDependency
// to Character.Health via the characterHealthBinding Binding
var bindingExpression =
BindingOperations.SetBinding(CharacterAttributes,
MyCharacterAttributes.HealthDependency,
characterHealthBinding);
// Store the binding so we can look it up if necessary in a
// List<BindingExpressionBase> in our CharacterAttributes class,
// and so it "lives" as long as CharacterAttributes does, too
CharacterAttributes.Bindings.Add(bindingExpression);
}
private void HitChracter_Button(object sender, RoutedEventArgs e)
{
CharacterAttributes.HealthValue -= 10.0;
}
private void DrinkHealth_Button(object sender, RoutedEventArgs e)
{
Character.DrinkHealthPotion(20.0);
}
Щелчок по кнопке HitCharacter уменьшает свойство CharacterAttributes.HealthValue
на 10. Это вызывает событие, которое через связывание, которое мы установили ранее, также вычитает 10.0 из значения Character.Health
. Нажатие кнопки DrinkHealth восстанавливает Character.Health
на 20.0, а также увеличивает CharacterAttributes.HealthValue
на 20.0.
Также обратите внимание, что этот материал действительно испечен в инфраструктуре пользовательского интерфейса - FrameworkElement
(который наследует от UIElement
) имеет SetBinding
и GetBinding
, реализованные на нем. Что имеет смысл - элементы графического интерфейса DataBinding - это совершенно допустимый сценарий для пользовательских интерфейсов! Если вы посмотрите глубже, то SetValue
, например, просто вызывает BindingOperations.SetBinding
на внутреннем интерфейсе, поэтому мы можем реализовать его, не имея при этом необходимости использовать UIElement
(как в примере выше). Однако одна из зависимостей, которую мы должны переносить, это DependencyObject
и DependencyProperty
- они обязательны для работы DataBinding, но, пока ваши объекты наследуются от DependencyObject
, вам не нужно никуда идти рядом с текстовым полем: -)
Однако недостатком является то, что некоторые вещи Binding были реализованы с помощью методов internal
, поэтому вы можете столкнуться с сценариями, в которых действия привязки, которые вы хотите реализовать, могут потребовать, чтобы вы написали дополнительный код, потому что вы просто не можете доступ к реализациям фреймворка, как родные классы. Однако, как было показано, возможно, что привязка данных TwoWay, подобная приведенному выше примеру, вполне возможна.