Customise a Windows Metadata File
The Experiment - Post-Build Weaving
public sealed class ShellViewModel : ViewModelBase
{
private string someProperty;
public string SomeProperty
{
get { return someProperty; }
set
{
someProperty = value;
OnPropertyChanged("SomeProperty");
}
}
}
As a jaded XAML developer, I want to do this:
public sealed class ShellViewModel : INotifyPropertyChanged
{
public string SomeProperty { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
and have the framework generate the plumbing code. Because I'm getting too old for this shit.First Step: Add the build task
<Project>
<!-- existing project file elements -->
<UsingTask TaskName="NotifyPropertyWeaverMsBuildTask.WeavingTask"
AssemblyFile="$(SolutionDir)lib\NotifyPropertyWeaverMsBuildTask.dll" />
<Target Name="AfterCompile">
<NotifyPropertyWeaverMsBuildTask.WeavingTask />
</Target>
</Project>
And that's it. By default, NotifyPropertyWeaver will inject code into all properties of types in this project, which implement INotifyPropertyChanged. There's a number of options available, if you want more control over the process.
Step Two: Push the Red Button
Step Three: Everybody freak out
No assembly? And who put this .winmd file here? My world has been inverted! I don't know what to believe in any more...
Take a deep breath, Alice. The adventure is just beginning.
Step Four: To the rabbit hole!
You should be greeted with a lovely screen straight out of the 90s:
Once you stop sniggering at how "I can build a better GUI than this", click File | Open and navigate to your bin/Debugoutput folder.
You will need to switch the File Type to Any Type (*.*) to see the files.
Select the .winmd file and open it.
Step Five: The cake is a lie!
- WinMD files use the same metadata format as .NET assemblies
- The single class from my C# source code is now split into three separate classes:
- A generated interface named IShellViewModelClass
- The public sealed class ShellViewModel as before
- A generated internal sealed class called <CLR>ShellViewModel
- If you inspect the methods of the ShellViewModel class, you'll see that there is no implementation.
To speed this up, I'll show you the disassembled code from dotPeek and highlight in bold the interesting parts.
[CompilerGenerated]
[Guid(1355171045U, (ushort) 32902, (ushort) 21012, (byte) 90, (byte) 248, (byte) 104, (byte) 96, (byte) 78, (byte) 141, (byte) 157, (byte) 215)]
[Version(16777216U)]
[ExclusiveTo(typeof (ShellViewModel))]
internal interface IShellViewModelClass
{
string SomeProperty
{
[MethodImpl(MethodCodeType = MethodCodeType.Runtime)] get;
[MethodImpl(MethodCodeType = MethodCodeType.Runtime)] set;
}
event PropertyChangedEventHandler PropertyChanged;
[MethodImpl(MethodCodeType = MethodCodeType.Runtime)]
void OnPropertyChanged([In] string propertyName);
}
[Version(16777216U)]
[CompilerGenerated]
[Activatable(16777216U)]
[SpecialName]
public sealed class ShellViewModel : INotifyPropertyChanged, IShellViewModelClass
{
public string SomeProperty
{
[MethodImpl(MethodCodeType = MethodCodeType.Runtime)] get;
[MethodImpl(MethodCodeType = MethodCodeType.Runtime)] set;
}
public event PropertyChangedEventHandler PropertyChanged;
[MethodImpl(MethodCodeType = MethodCodeType.Runtime)]
public ShellViewModel();
[MethodImpl(MethodCodeType = MethodCodeType.Runtime)]
public void OnPropertyChanged([In] string propertyName);
}
I won't dig into the specifics of these attributes until I've fully grasped the intent of them - the [Guid] attribute feels COM-ish, and the [Version] attribute might be a hash of the current class definition (to prevent clashes) - but I'm only speculating here.Also note how the tooling (this is winmdexp.exe) is creating the classes while specifying the implementation is "missing" by using the
[MethodImpl] attribute.
[Version(16777216U)]
[Activatable(16777216U)]
[SpecialName]
internal sealed class <CLR>ShellViewModel : INotifyPropertyChanged, IShellViewModelClass
{
private EventRegistrationTokenTable<PropertyChangedEventHandler> PropertyChanged = new EventRegistrationTokenTable<PropertyChangedEventHandler>();
public string SomeProperty
{
get { return this.<SomeProperty>k__BackingField; }
set
{
if (object.Equals((object) this.<SomeProperty>k__BackingField, (object) value))
return;
this.<SomeProperty>k__BackingField = value;
this.OnPropertyChanged("SomeProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged
{
add { return this.PropertyChanged.AddEventHandler(value); }
remove { this.PropertyChanged.RemoveEventHandler(value); }
}
public void OnPropertyChanged(string propertyName)
{
EventRegistrationTokenTable<PropertyChangedEventHandler>
registrationTokenTable = this.PropertyChanged;
if (registrationTokenTable == null)
return;
((PropertyChangedEventHandler) registrationTokenTable)((object) this, new PropertyChangedEventArgs(propertyName));
}
}
The CLR implementation looks somewhat familiar to our initial class but with some additional behaviour:
- Comparison check in the setter to raise event only when value changes
- Generated OnPropertyChanged call with the property name set correctly
- Implementation of the OnPropertyChanged method
By integrating it into the AfterCompile step way back when our code was compiled, we can inject behaviour which satisfies the winmdexp.exe process without cluttering our source code with it.
Step Six: Recap - and hard liquor!
- I suspect there are easy ways to get around the whole "no generic methods" with MVVM frameworks WinRT libraries - I have some ideas in mind, but am waiting to see what the other guys (has Laurent put something out for WinRT - I know he's up to something)
- Don't be afraid to automate the mundane bits. This is all using MSBuild under the hood to compose the library, and required a couple of tweaks due to changes with the build process.
- IL Rewriting - in its various forms - to inject AOP-style behaviour will likely carry on unabated. Excellent.
Special Mention
Hopefully I can help him with some patches to get this version available for mass consumption. But if you're doing WPF/SL/WP7 apps currently, you can use the tool today and get a whole bunch more benefits.