Wednesday, October 11, 2017

In my post on Xamarin device orientation I mentioned a new way to reduce the repetitive property declarations that plague MVVM projects. Unfortunately this new technique was not introduced until Framework 4.5. However I came across a simple way to implement it in earlier frameworks.

Here is an simplified project that shows the CallerMemberName attribute being used in a Framework 4.0 project.

Start a new WPF application called CompilerServicesIn4 and target it for Framework 4.0.



Now let's do the magic part. Add a new class called CompilerServices and make the code look like this.

namespace System.Runtime.CompilerServices
{
    sealed class CallerMemberName : Attribute { }
}

This adds a new attribute class to the CompilerServices namespace. Now let's prove that it works. We will create a MainWindow that has a textbox and a textblock both bound to the same property. As we type into the textbox the text will be shown in the textblock.

Make MainWindow.xaml look like this.

<Window x:Class="CompilerServicesIn4.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:CompilerServicesIn4"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        DataContext="{Binding RelativeSource={RelativeSource Self}}">
    <StackPanel Orientation="Horizontal" Height="20">
        <TextBox Width="100" Text="{Binding SomeText, UpdateSourceTrigger=PropertyChanged}"/>
        <TextBlock Text="{Binding SomeText}"/>
    </StackPanel>
</Window>

Now make MainWindow.xaml.cs look like this. Note this is not an optimal architecture, you would normally break PropertyChanged, SetProperty, and OnPropertyChanged out into a base class.

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;

namespace CompilerServicesIn4
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private String _SomeText = "";
        public String SomeText
        {
            get { return _SomeText; }
            set { SetProperty(ref _SomeText, value); }
        }
        public MainWindow()
        {
            InitializeComponent();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(storage, value)) return false;
            storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }

        private void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}


If you don't believe me, try commenting out the line starting with sealed class in CompilerServices.cs. You will no longer be able to build unless you target Framework 4.5.

Note: In VB the extra class looks like this.

Namespace Global.System.RunTime.CompilerServices
    NotInheritable Class CallerMemberName
        Inherits Attribute
    End Class
End Namespace

No comments:

Post a Comment