Общие сведения об объектах класса Freezable

В этом разделе описаны принципы эффективного использования и создания объектов Freezable, предоставляющих специальные функции для повышения производительности приложения. Примерами объектов класса Freezable являются кисти, ручки, преобразования, геометрии и анимации.

Что такое объект класса Freezable?

Freezable — это особый тип объекта, имеющий два состояния: фиксированное и нефиксированное. В нефиксированном состоянии Freezable ведет себя аналогично любому другому объекту. В фиксированном состоянии изменить Freezable больше нельзя.

Freezable предоставляет событие Changed для уведомления наблюдателей о любых изменениях объекта. Фиксация объекта Freezable может повысить его производительность, поскольку больше не требуется тратить ресурсы на уведомления об изменениях. Фиксированные объекты Freezable могут также совместно использоваться потоками, а нефиксированные Freezable — нет.

Несмотря на то что у класса Freezable множество приложений, большинство объектов Freezable в Windows Presentation Foundation (WPF) связаны с подсистемой графики.

Класс Freezable упрощает использование определенных графических системных объектов и помогает повысить производительность приложения. К примерам типов, которые наследуются от Freezable, относятся такие классы, как Brush, Transform и Geometry. Поскольку они содержат неуправляемые ресурсы, система должна отслеживать эти объекты на предмет изменений, а затем при изменении исходного объекта обновлять соответствующие неуправляемые ресурсы. Даже если вы нас самом деле не изменяете графический системный объект, система все равно должна тратить часть ресурсов на мониторинг объекта на случай, если вы его измените.

Представим для примера, что вы создаете кисть SolidColorBrush и используете ее для раскраски фона кнопки.

Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);
myButton.Background = myBrush;
Dim myButton As New Button()
Dim myBrush As New SolidColorBrush(Colors.Yellow)
myButton.Background = myBrush

При отрисовке кнопки подсистема графики WPF использует предоставленные сведения для рисования группы пикселей и создания внешнего вида кнопки. Несмотря на то что для описания принципа раскрашивания кнопки вы использовали одноцветную кисть, на самом деле одноцветная кисть раскрашиванием не занимается. Графическая система создает быстрые низкоуровневые объекты для кнопки и кисти: именно эти объекты на самом деле отображаются на экране.

Если бы вы изменили кисть, эти низкоуровневые объекты пришлось бы создавать повторно. Класс Freezable дает кисти возможность находить соответствующие созданные низкоуровневые объекты и обновлять их при изменении. Если эта возможность включена, кисть является "нефиксированной".

Метод Freeze класса Freezable позволяет отключить эту возможность самостоятельного обновления. С помощью этого метода можно сделать кисть "фиксированной", или неизменяемой.

Примечание

Не всякий объект класса Freezable может быть зафиксирован. Чтобы избежать исключения InvalidOperationException, проверьте значение свойства CanFreeze объекта класса Freezable. Это позволяет определить, можно ли его зафиксировать, прежде чем попытаться это сделать.

if (myBrush.CanFreeze)
{
    // Makes the brush unmodifiable.
    myBrush.Freeze();
}
If myBrush.CanFreeze Then
    ' Makes the brush unmodifiable.
    myBrush.Freeze()
End If

Если изменять объект класса Freezable больше не требуется, его фиксация обеспечивает преимущества в плане производительности. Если бы вы зафиксировали кисть в данном примере, графической системе больше не требовалось бы отслеживать ее на предмет изменений. Графическая система может также выполнять другие оптимизации, так как знает, что кисть не изменится.

Примечание

Для удобства объекты класса Freezable, если не зафиксировать их явным образом, остаются нефиксированными.

Использование объектов класса Freezable

Использование нефиксированных объектов класса Freezable аналогично использованию объектов любого другого типа. В следующем примере цвет кисти SolidColorBrush изменяется с желтого на красный после того, как ее используют для раскрашивания фона кнопки. Графическая система работает в фоновом режиме, и при следующем обновлении экрана цвет кнопки автоматически меняется с желтого на красный.

Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);
myButton.Background = myBrush;

// Changes the button's background to red.
myBrush.Color = Colors.Red;
Dim myButton As New Button()
Dim myBrush As New SolidColorBrush(Colors.Yellow)
myButton.Background = myBrush


' Changes the button's background to red.
myBrush.Color = Colors.Red

Фиксация объекта класса Freezable

Чтобы сделать объект Freezable неизменяемым, вызовите его метод Freeze. При фиксации объекта, содержащего объекты класса Freezable, эти объекты также фиксируются. Например, при фиксации объекта PathGeometry фигуры и сегменты, содержащиеся в нем, также фиксируются.

Объект класса Freezable нельзя зафиксировать, если верно любое из следующих утверждений.

  • Он имеет анимированные свойства или свойства с привязкой к данным.

  • Он имеет свойства, заданные динамическим ресурсом. (Дополнительные сведения о динамических ресурсах см. в разделе Ресурсы XAML.)

  • Он содержит вложенные объекты Freezable, которые нельзя зафиксировать.

Если эти условия не выполняются и вы не планируете изменять объект Freezable, его следует зафиксировать, чтобы получить преимущества по производительности, описанные ранее.

После вызова метода Freeze класса Freezable изменить его больше будет нельзя. Попытка изменить зафиксированный объект приводит к исключению InvalidOperationException. В следующем коде создается исключение, так как мы пытаемся изменить кисть после ее фиксации.


Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);

if (myBrush.CanFreeze)
{
    // Makes the brush unmodifiable.
    myBrush.Freeze();
}

myButton.Background = myBrush;

try {

    // Throws an InvalidOperationException, because the brush is frozen.
    myBrush.Color = Colors.Red;
}catch(InvalidOperationException ex)
{
    MessageBox.Show("Invalid operation: " + ex.ToString());
}


Dim myButton As New Button()
Dim myBrush As New SolidColorBrush(Colors.Yellow)

If myBrush.CanFreeze Then
    ' Makes the brush unmodifiable.
    myBrush.Freeze()
End If

myButton.Background = myBrush

Try

    ' Throws an InvalidOperationException, because the brush is frozen.
    myBrush.Color = Colors.Red
Catch ex As InvalidOperationException
    MessageBox.Show("Invalid operation: " & ex.ToString())
End Try

Чтобы избежать этого исключения, можно использовать метод IsFrozen, который позволяет определить, зафиксирован ли объект Freezable.


Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);

if (myBrush.CanFreeze)
{
    // Makes the brush unmodifiable.
    myBrush.Freeze();
}

myButton.Background = myBrush;

if (myBrush.IsFrozen) // Evaluates to true.
{
    // If the brush is frozen, create a clone and
    // modify the clone.
    SolidColorBrush myBrushClone = myBrush.Clone();
    myBrushClone.Color = Colors.Red;
    myButton.Background = myBrushClone;
}
else
{
    // If the brush is not frozen,
    // it can be modified directly.
    myBrush.Color = Colors.Red;
}


Dim myButton As New Button()
Dim myBrush As New SolidColorBrush(Colors.Yellow)

If myBrush.CanFreeze Then
    ' Makes the brush unmodifiable.
    myBrush.Freeze()
End If

myButton.Background = myBrush


If myBrush.IsFrozen Then ' Evaluates to true.
    ' If the brush is frozen, create a clone and
    ' modify the clone.
    Dim myBrushClone As SolidColorBrush = myBrush.Clone()
    myBrushClone.Color = Colors.Red
    myButton.Background = myBrushClone
Else
    ' If the brush is not frozen,
    ' it can be modified directly.
    myBrush.Color = Colors.Red
End If


В предыдущем примере кода изменяемая копия была создана на основе зафиксированного объекта с помощью метода Clone. В следующем разделе клонирование будет рассмотрено подробнее.

Примечание

Поскольку зафиксированный объект класса Freezable анимировать нельзя, система анимации автоматически создаст изменяемые клоны зафиксированных объектов Freezable при попытке анимировать их с помощью Storyboard. Чтобы исключить большой расход ресурсов в результате клонирования, оставляйте объект нефиксированным, если планируется анимировать его. Дополнительные сведения об анимации с помощью раскадровок см. в статье Общие сведения о раскадровках.

Фиксация на основе разметки

Чтобы зафиксировать объект Freezable, объявленный в разметке, используется атрибут PresentationOptions:Freeze. В следующем примере объект SolidColorBrush объявляется как ресурс страницы и фиксируется. Затем он используется для задания фона кнопки.

<Page 
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" 
  xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="PresentationOptions">

  <Page.Resources>

    <!-- This resource is frozen. -->
    <SolidColorBrush 
      x:Key="MyBrush"
      PresentationOptions:Freeze="True" 
      Color="Red" />
  </Page.Resources>


  <StackPanel>

    <Button Content="A Button" 
      Background="{StaticResource MyBrush}">
    </Button>

  </StackPanel>
</Page>

Чтобы использовать атрибут Freeze, необходимо сопоставить пространство имен параметров презентации: http://schemas.microsoft.com/winfx/2006/xaml/presentation/options. PresentationOptions — рекомендуемый префикс для сопоставления этого пространства имен:

xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options"

Так как не все читатели XAML распознают этот атрибут, рекомендуется использовать атрибут mc:Ignorable, чтобы пометить атрибут Presentation:Freeze как игнорируемый:

xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="PresentationOptions"

Дополнительные сведения см. на странице Атрибут mc:Ignorable.

Отмена фиксации объекта класса Freezable

После фиксации объекта Freezable изменить его или отменить его фиксацию нельзя. Однако можно создать нефиксированный клон с помощью метода Clone или CloneCurrentValue.

В следующем примере фон кнопки задается с помощью кисти, после чего эта кисть фиксируется. На основе кисти с помощью метода Clone создается нефиксированная копия. Клон изменяется и используется для изменения фона кнопки с желтого на красный.

Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);

// Freezing a Freezable before it provides
// performance improvements if you don't
// intend on modifying it.
if (myBrush.CanFreeze)
{
    // Makes the brush unmodifiable.
    myBrush.Freeze();
}

myButton.Background = myBrush;

// If you need to modify a frozen brush,
// the Clone method can be used to
// create a modifiable copy.
SolidColorBrush myBrushClone = myBrush.Clone();

// Changing myBrushClone does not change
// the color of myButton, because its
// background is still set by myBrush.
myBrushClone.Color = Colors.Red;

// Replacing myBrush with myBrushClone
// makes the button change to red.
myButton.Background = myBrushClone;
Dim myButton As New Button()
Dim myBrush As New SolidColorBrush(Colors.Yellow)

' Freezing a Freezable before it provides
' performance improvements if you don't
' intend on modifying it. 
If myBrush.CanFreeze Then
    ' Makes the brush unmodifiable.
    myBrush.Freeze()
End If


myButton.Background = myBrush

' If you need to modify a frozen brush,
' the Clone method can be used to
' create a modifiable copy.
Dim myBrushClone As SolidColorBrush = myBrush.Clone()

' Changing myBrushClone does not change
' the color of myButton, because its
' background is still set by myBrush.
myBrushClone.Color = Colors.Red

' Replacing myBrush with myBrushClone
' makes the button change to red.
myButton.Background = myBrushClone

Примечание

Независимо от используемого метода клонирования анимации никогда не копируются в новый объект Freezable.

Методы Clone и CloneCurrentValue создают глубокие копии объекта класса Freezable. Если объект класса Freezable содержит другие фиксированные объекты класса Freezable, они также клонируются и делаются изменяемыми. Например, при клонировании фиксированного объекта PathGeometry с целью сделать его изменяемым фигуры и сегменты, которые он содержит, также копируются и делаются изменяемыми.

Создание собственного класса Freezable

Класс, производный от Freezable, получает следующие признаки.

  • Специальные состояния: состояние только для чтения (фиксированное) и записываемое состояние.

  • Потокобезопасность: фиксированный объект Freezable может совместно использоваться потоками.

  • Подробное уведомление об изменении: в отличие от других объектов DependencyObject объекты класса Freezable при изменении значений вложенных свойств отправляют уведомления об изменении.

  • Простое клонирование: класс Freezable уже реализовал несколько методов, которые создают глубокие клоны.

Freezable — это тип DependencyObject. Следовательно, он использует систему свойств зависимостей. Необязательно, чтобы свойства класса были свойствами зависимостей, однако использование свойств зависимостей уменьшит объем кода, который необходимо написать, так как класс Freezable разработан с учетом свойств зависимостей. Дополнительные сведения о системе свойств зависимостей см. в разделе Общие сведения о свойствах зависимостей.

Каждый вложенный класс Freezable должен переопределять метод CreateInstanceCore. Если класс использует свойства зависимостей для всех своих данных, все готово.

Если класс содержит элементы данных свойств, не являющихся свойствами зависимостей, необходимо также переопределить следующие методы:

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

  • В начале любого API, который считывает элементы данных, не являющиеся свойствами зависимостей, вызовите метод ReadPreamble.

  • В начале любого API, который записывает элементы данных, не являющиеся свойствами зависимостей, вызовите метод WritePreamble. (После вызова WritePreamble в API не нужно выполнять дополнительный вызов к ReadPreamble, если вы также читаете элементы данных, не являющихся свойствами зависимостей.)

  • Вызовите метод WritePostscript до выхода из методов, которые выполняют запись в элементы данных, не являющиеся свойствами зависимостей.

Если класс содержит элементы данных, не являющиеся свойствами зависимостей и являющиеся объектами DependencyObject, необходимо также вызывать метод OnFreezablePropertyChanged каждый раз при изменении одного из их значений, даже если для элемента задано значение null.

Примечание

Очень важно начинать каждый переопределяемый метод Freezable с вызова базовой реализации.

Пример пользовательского класса Freezableсм. в разделе Пример пользовательской анимации.

См. также раздел