Obiekty Freezable

WPF dostarcza obiekt Freezable. Możemy dziedziczyć po tej klasie aby stworzyć własne obiekty immutable. Ze względów wydajnościowych, można takowy obiekt “zamrozić” i wtedy nie można już go więcej modyfikować – staje się więc immutable.  Przykład:

internal class SampleClass : Freezable
{
   public SampleClass(string text)
   {
       Text = text;
   }

   protected override Freezable CreateInstanceCore()
   {
       return new SampleClass(null);
   }

   public static readonly DependencyProperty TextProperty =
       DependencyProperty.Register("Text", typeof (string), typeof (SampleClass));

   public string Text
   {
       get { return this.GetValue(TextProperty) as string; }
       set { this.SetValue(TextProperty, value); }
   }
}

Przykład nie jest zbyt skomplikowany. Dziedziczymy po Freezable i implementujemy CreateInstanceCore, która będzie służyć do klonowania obiektów ( o tym za chwilę). Aby zamrozić obiekt wystarczy wywołać metodę Freeze:

var sampleClass = new SampleClass("tekst");
sampleClass.Freeze();
sampleClass.Text = "Nie da rady...";

Po zamrożeniu, próba modyfikacji na właściwości Text zakończy się wyjątkiem InvalidOperationException: “Cannot set a property on object ‘WpfApplication2.SampleClass’ because it is in a read-only state.” Freezable sam dba o zabronienie dostępu do właściwości pod warunkiem, że są one typu DependencyProperties. W innym przypadku sprawa wygląda bardziej skomplikowanie. Wiemy, że próba zakończy się wyjątkiem zatem bezpieczniej w kodzie sprawdzać w jakim stanie jest aktualnie obiekt:

if(sampleClass.IsFrozen==false)
    sampleClass.Text = "Value";

Obiekt raz zamrożony nie może zostać odblokowany. Jedyna możliwość to sklonowanie jego np:

var sampleClass = new SampleClass("tekst");
if(sampleClass.CanFreeze)
    sampleClass.Freeze();

var clonedObject = (SampleClass)sampleClass.Clone();
MessageBox.Show(clonedObject.IsFrozen.ToString());// po sklonowaniu, ponownie false
MessageBox.Show(clonedObject.Text);

Clone wykonuje głęboką kopie obiektu. Zaimplementowana prze nas metoda CreateInstanceCore tworzy jedynie instancję obiektu – wszystkie właściwości zostaną sklonowane za nas automatycznie. CreateInstnaceCore ogranicza się zwykle do wywołania domyślnego konstruktora – reszta zostanie wykonana przez Freezable.

Freezable dostarcza również zdarzenie Changed, przydatne podczas monitorowania stanu obiektu:

private void Button_Click(object sender, RoutedEventArgs e)
{
  var sampleClass = new SampleClass("tekst");
  sampleClass.Changed += new EventHandler(SampleClassChanged);
  sampleClass.Text = "Zmiana tekstu";
}
void SampleClassChanged(object sender, EventArgs e)
{
}

Jakie ma to praktyczne zastosowanie? Klasa Freezable została dodana głównie ze względów wydajnościowych. Zamrożony obiekt nie zmieni swojego stanu zatem nie musimy go obserwować. Na przykład kontrolki w WPF muszą być przerenderowane gdy któryś z obiektów zmienił swój stan. Wspomniane wcześniej zdarzenie Changed nie będzie musiało już być monitorowane. Klasy zamrożone są oczywiste bezpieczne z punktu widzenia wielowątkowości ponieważ mamy pewność, że właściwości nie zostaną zmodyfikowane z innego wątku. Dzięki temu, nie trzeba używać czasochłonnych blokad (lock) lub innych metod synchronizacji.

Freezable jest powszechny w kontrolkach WPF. Na przykład zdarzenie Brush.Changed  jest śledzone i w momencie jego wywołania, kontrolka jest przerenderowywana z nowym kolorem. Inne przykłady to klasy Transform , Geometry czy animacje. Wszystkie pędzle systemowe są domyślnie zamrożone. Nie możliwe jest na przykład zmiana koloru dla jakiekolwiek systemowego pędzla:

SolidColorBrush systemBrush = Brushes.White;
MessageBox.Show(systemBrush.IsFrozen.ToString()); // zawsze true

SolidColorBrush customBrush=new SolidColorBrush(Colors.AliceBlue);
MessageBox.Show(customBrush.IsFrozen.ToString()); // domyslnie false

Istnieje również możliwość zamrożenia obiektu prosto z poziomu XAML. Załóżmy, że definiujemy Brush w zasobach:

<LinearGradientBrush ice:Freeze="True" 
xmlns:ice="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" 
/>

Leave a Reply

Your email address will not be published.