Свойство зависимости в пользовательском элементе управления неожиданно совместно использует память/значения

У меня настроено следующее:

  • Пользовательский элемент управления WPF (базовый класс), производный от Canvas
  • Реализация этого базового класса
  • Свойство зависимости ObservableCollection<T> для этой реализации

У меня есть тестовое приложение, которое отображает три уникальных экземпляра моего пользовательского элемента управления (например, <custom:MyControl x:Name="Test1" />, Test2, Test3 и т. д.). Когда я запускаю и отлаживаю приложение, содержимое ObservableCollection<T> одинаково для всех трех экземпляров элемента управления. Почему это?


Диаграмма:

[ContentProperty("DataGroups")]
public abstract class Chart : Canvas
{
    static Chart()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(Chart), new FrameworkPropertyMetadata(typeof(Chart)));
    }

    public ObservableCollection<ChartData> DataGroups
    {
        get { return (ObservableCollection<ChartData>)GetValue(DataGroupsProperty); }
        set { SetValue(DataGroupsProperty, value); }
    }
    public static readonly DependencyProperty DataGroupsProperty =
        DependencyProperty.Register("DataGroups", typeof(ObservableCollection<ChartData>), typeof(Chart), new FrameworkPropertyMetadata(new ObservableCollection<ChartData>(), FrameworkPropertyMetadataOptions.AffectsArrange));

    public abstract void Refresh();
}

Данные диаграммы:

[ContentProperty("Points")]
public class ChartData : FrameworkElement
{
    public ObservableCollection<Point> Points
    {
        get { return (ObservableCollection<Point>)GetValue(PointsProperty); }
        set { SetValue(PointsProperty, value); }
    }
    public static readonly DependencyProperty PointsProperty =
        DependencyProperty.Register("Points", typeof(ObservableCollection<Point>), typeof(ChartData), new PropertyMetadata(new ObservableCollection<Point>()));
}

Один из способов изменить данные диаграммы (предполагая наличие нескольких групп данных), например:

MyChart.DataGroups[index].Points.Add(new Point() { Y = someNumber });
MyChart.Refresh();

Но каждый экземпляр внутри DataGroups[] идентичен.


То же самое происходит, если я определяю свои коллекции через XAML, например так:

<c:Chart x:Name="ChartA">
    <c:ChartData x:Name="DataGroup1" />
    <c:ChartData x:Name="DataGroup2" />
</c:Chart>

Затем в коде я бы получил доступ к определенным коллекциям:

ChartA.DataGroups[0].Points.Add(new Point() { Y = someNumber });
ChartA.Refresh();

person qJake    schedule 07.10.2013    source источник
comment
Вы должны показать нам код. Как выглядит объявление свойства зависимости? Как вы его связываете?   -  person Gabe    schedule 07.10.2013
comment
Код добавлен, хотя он очень частичный (я пытался извлечь соответствующие биты, насколько мог, я не могу просто вставить все это сюда, потому что реализация огромна, и многое из этого не имеет значения).   -  person qJake    schedule 07.10.2013
comment
Откуда Points? Как он назначается?   -  person Gabe    schedule 07.10.2013
comment
Нижняя часть вопроса объясняет, где он определен и как он назначается, хотя вероятность того, что он может быть назначен через XAML, так же высока, как и через код (из-за атрибута [ContentProperty]). А Point это всего лишь System.Windows.Point.   -  person qJake    schedule 07.10.2013
comment
Итак, если у вас есть Test1.Points = new ObservableCollection<Point>(); Test2.Points = new ObservableCollection<Point>(); Test1.Points.Add(new Point());, то Test2.Points будет иметь ту же точку, что и добавленная к Test1?   -  person Gabe    schedule 07.10.2013
comment
Нет, несмотря на то, что свойства зависимостей являются статическими, их резервные свойства не являются таковыми, и обычно вы все равно не устанавливаете свойство Points таким образом, потому что в FrameworkPropertyMetadata вы указываете значение по умолчанию, которое я все равно установил на new ObservableCollection<Point>(). Кроме того, дублируются не только точки, есть и другие (не показанные) свойства в ChartData, которые также дублируются. По сути, это public ObservableCollection<ChartData> DataGroups, который действует статично, хотя на самом деле это не так.   -  person qJake    schedule 07.10.2013
comment
Хорошо, вот твой ответ.   -  person Gabe    schedule 07.10.2013


Ответы (2)


Вы не сделали ничего плохого. Это по дизайну. Это должно работать таким образом. Вместо этого просто установите свое значение в конструкторе, и у вас не будет синглтона.

http://msdn.microsoft.com/en-us/library/aa970563.aspx

Инициализация коллекции за пределами значения по умолчанию

При создании свойства зависимостей вы не указываете значение свойства по умолчанию в качестве начального значения поля. Вместо этого вы указываете значение по умолчанию через метаданные свойства зависимостей. Если ваше свойство является ссылочным типом, значение по умолчанию, указанное в метаданных свойства зависимостей, не является значением по умолчанию для каждого экземпляра; вместо этого это значение по умолчанию, которое применяется ко всем экземплярам типа. Поэтому вы должны быть осторожны, чтобы не использовать единственную статическую коллекцию, определенную метаданными свойства коллекции, в качестве рабочего значения по умолчанию для вновь созданных экземпляров вашего типа. Вместо этого вы должны убедиться, что вы намеренно установили значение коллекции в уникальную коллекцию (экземпляр) как часть логики конструктора вашего класса. В противном случае вы создадите непреднамеренный одноэлементный класс.

person dev hedgehog    schedule 07.10.2013
comment
Отлично, это решило это. Я не знал, как работают значения свойства зависимостей по умолчанию. Спасибо! - person qJake; 09.10.2013

PointsProperty — это статическое значение, которое вы инициализируете значением по умолчанию new ObservableCollection<Point>(). Этот статический инициализатор создает один ObservableCollection и использует его как значение по умолчанию для Points для любого объекта типа ChartData, который вы создаете. Это не фабрика, которая создает новые ObservableCollection для каждого экземпляра, которому требуется значение по умолчанию; он просто использует один и тот же ObservableCollection для каждого.

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

person Gabe    schedule 07.10.2013
comment
Вы оба были правы, но я выбрал аутсайдера с 200 баллами вместо 44 000. Надеюсь, вы понимаете. :) Спасибо за вашу помощь! - person qJake; 09.10.2013