I'm currently working on setting up a new project o开发者_StackOverflow中文版f mine and was wondering how I could achieve that my ViewModel classes do have INotifyPropertyChanged support while not having to handcode all the properties myself.
I looked into AOP frameworks but I think they would just blow up my project with another dependency.
So I thought about generating the property implementations with T4.
The setup would be this: I have a ViewModel class that declares just its Properties background variables and then I use T4 to generate the Property Implementations from it.
For example this would be my ViewModel:
public partial class ViewModel
{
private string p_SomeProperty;
}
Then T4 would go over the source file and look for member declarations named "p_" and generate a file like this:
public partial class ViewModel
{
public string SomeProperty
{
get
{
return p_SomeProperty;
}
set
{
p_SomeProperty= value;
NotifyPropertyChanged("SomeProperty");
}
}
}
This approach has some advantages but I'm not sure if it can really work. So I wanted to post my idea here on StackOverflow as a question to get some feedback on it and maybe some advice how it can be done better/easier/safer.
Here's a great post by Colin Eberhardt on generating Dependency Properties from a T4 by inspecting custom attributes directly from Visual Studio with EnvDTE. It shouldn't be difficult to adapt it to inspect fields and generate code appropriately since the post contains simple utility methods to browse code nodes.
Note that when using T4 from VS, you shouldn't use Reflection on your own assemblies or they'll get locked and you'll have to restart Visual Studio in order to rebuild.
There are a lot of ways to skin this cat.
We have been toying with PostSharp to inject the INotifyProperty boilerplate. That seems to work pretty good.
That being said, there's no reason why T4 wouldn't work.
I agree with Dan that you should create the base class implementation of OnPropertyChanged.
Have you considered just using a code snippet? It will write the boilerplate for you. The only disadvantage is that it will not update automatically if you want to change the property name at some later date.
<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
<CodeSnippet Format="1.0.0">
<Header>
<Title>propin</Title>
<Shortcut>propin</Shortcut>
<Description>Code snippet for property and backing field with support for INotifyProperty</Description>
<SnippetTypes>
<SnippetType>Expansion</SnippetType>
</SnippetTypes>
</Header>
<Snippet>
<Declarations>
<Literal>
<ID>type</ID>
<ToolTip>Property type</ToolTip>
<Default>int</Default>
</Literal>
<Literal>
<ID>property</ID>
<ToolTip>Property name</ToolTip>
<Default>MyProperty</Default>
</Literal>
</Declarations>
<Code Language="csharp">
<![CDATA[private $type$ _$property$;
public $type$ $property$
{
get { return _$property$;}
set
{
if (value != _$property$)
{
_$property$ = value;
OnPropertyChanged("$property$");
}
}
}
$end$]]>
</Code>
</Snippet>
</CodeSnippet>
</CodeSnippets>
It should definitely work.
I'd recommend first writing a base class that implements INotifyPropertyChanged
, giving it a protected void OnPropertyChanged(string propertyName)
method, making it cache its PropertyChangeEventArgs
objects (one per unique property name -- no point in creating a new object every time the event is raised), and have your T4-generated class derive from this base.
To get the members that need properties implemented, you can just do something like:
BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
FieldInfo[] fieldsNeedingProperties = inputType.GetFields(flags)
.Where(f => f.Name.StartsWith("p_"))
.ToArray();
And go from there:
<# foreach (var field in fieldsNeedingProperties) { #>
<# string propertyName = GetPropertyName(field.Name); #>
public <#= field.FieldType.FullName #> <#= propertyName #> {
get { return <#= field.Name #>; }
set {
<#= field.Name #> = value;
OnPropertyChanged("<#= propertyName #>");
}
}
<# } #>
<#+
private string GetPropertyName(string fieldName) {
return fieldName.Substring(2, fieldName.Length - 2);
}
#>
And so on.
精彩评论