Freeze属性について

WPFでオブジェクトが固定であるか設定するFreeze属性がどれくらいの強制力を持ってオブジェクトを固定できるか検証したため、まとめておきます。

# 検証内容
検証用のコードは以下

MainWindow.xaml
```xml
<Window x:Class="FreezeTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:FreezeTest"
        xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" 
        Title="MainWindow" Height="100" Width="800">
    <Window.Resources>
        <local:StringColorConverter x:Key="StringColorConverter" />
        <Color x:Key="BackgroundColor">#FF0000</Color>

       <!-- ① -->
        <SolidColorBrush x:Key="BackgroundColorBrush" Color="Red" PresentationOptions:Freeze="True" />

       <!-- ② -->
        <SolidColorBrush x:Key="BackgroundColorBrush" Color="{Binding Path=Text,ElementName=m_TextBox,Converter={StaticResource StringColorConverter}}" PresentationOptions:Freeze="True" />

       <!-- ③ -->
       <SolidColorBrush x:Key="BackgroundColorBrush" Color="{DynamicResource BackgroundColor}" PresentationOptions:Freeze="True" />

    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <TextBox x:Name="m_TextBox" Grid.Column="0" Background="{StaticResource BackgroundColorBrush}" />

       <!-- ① -->
        <Button x:Name="m_BrushColorChangeButton" Grid.Column="1" Content="ブラシの色変更" Click="m_BrushColorChangeButton_Click"/>

       <!-- ③ -->
       <Button x:Name="m_ResourceChangeButton" Grid.Column="1" Content="リソース変更" Click="m_ResourceChangeButton_Click" />
    </Grid>
</Window>
```

MainWindow.xaml.cs
```cs
using System.Windows;
using System.Windows.Media;

namespace FreezeTest
{
    /// <summary>
    /// メインウィンドウ
    /// </summary>
    public partial class MainWindow : Window
    {
        /// <summary>
        /// Brushのリソースキー
        /// </summary>
        private const string s_BrushResourceKey = "BackgroundColorBrush";

        /// <summary>
        /// Colorのリソースキー
        /// </summary>
        private const string s_ColorResourceKey = "BackgroundColor";

        /// <summary>
        /// コンストラクタ
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
        }

        /// <summary>
        /// ブラシの色変更ボタンを押したときのイベントハンドラ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void m_BrushColorChangeButton_Click(object sender, RoutedEventArgs e)
        {
            var brush = this.Resources[s_BrushResourceKey] as SolidColorBrush;

            if (brush == null)
                return;

            try
            {
                var color = (Color)ColorConverter.ConvertFromString(m_TextBox.Text);
                brush.Color = color;
            }
           catch(Exception ex)
            { // ここは文字列をColorに変更失敗したかブラシのColorの変更に失敗したときのみ来る
                MessageBox.Show(ex.Message, ex.GetType().ToString());
            }
        }

        /// <summary>
        /// リソース変更ボタンを押したときのイベントハンドラ
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void m_ResourceChangeButton_Click(object sender, RoutedEventArgs e)
        {
           Color color = Colors.Red;
            try
            {
                color = ColorConverter.ConvertFromString(m_TextBox.Text);
            }
            catch
            {   // ここは文字列をColorに変換失敗したときのみ来る
                return;
             }
            // リソースの色を変更 
            this.Resources.Remove(s_ColorResourceKey);
            this.Resources.Add(s_ColorResourceKey, color);
        }
    }
}
```

StringColorConverter.cs
```cs
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using Color = System.Windows.Media.Color;
using ColorConverter = System.Windows.Media.ColorConverter;

namespace FreezeTest
{
    internal class StringColorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var text = value as string;
            try
            {
                var color = ColorConverter.ConvertFromString(text);
                return color;
            }
            catch
            {
                return Colors.Red;
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if(value is Color)
            {
                var color = (Color)value;
                return color.ToString();
            }
                
            return string.Empty;
        }
    }
}
```
### コードビハインドで変更(①が有効、②、③をコメントアウト）した場合
テキストボックスに”Blue”と入力し、[ブラシの色変更]ボタンを押すと、InvalidOperationExceptionがthrowされ、Colorプロパティが変更できないことが通知される。

### Bindingで動的に変更できるように実装(②が有効、①、③をコメントアウト）した場合
テキストボックスに"Blue"と入力すると、例外は発生せず、テキストボックスの背景色が青になる（変更できる）。

### DynamicResourceで動的に変更できるように実装(③が有効、①、②をコメントアウト）した場合
テキストボックスに”Blue”と入力し、[リソース変更]ボタンを押すと、例外が発生せず、テキストボックスの背景色が青色になる（変更できる）。

# まとめ

MSDNでは以下の4つのケースでは固定(Freeze属性がTrue)に設定したとしても、固定しない(Freeze属性がFalse)として扱われる

1. アニメーションで変更対象になっているプロパティがある
1. データバインドされたプロパティがある
1. DynamicResourceで設定されるプロパティがある
1. 上記３つのいずれかを満たすサブオブジェクトを含む

※参考文献：[Freezableの使用](https://learn.microsoft.com/ja-jp/dotnet/desktop/wpf/advanced/freezable-objects-overview?view=netframeworkdesktop-4.8#using-freezables)

端的に言うと、**動的に値が変更できる仕組みを設定されたプロパティがあると固定できない**ということです。

また、検証結果から、固定できる場合に変更しようとすると、例外が発生し変更できないが、固定できなかった場合に変更しようとすると、例外が発生せずに変更できるということが分かります。

このことから、Freeze属性がTrueであるにもかかわらず動的に値が変更できる仕組みを設定するというコード上矛盾した状態を動作から判別できないため、コードの可読性を考えると注意が必要です。