views:

292

answers:

3

I'm currently working on setting up a new project of 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.

+1  A: 

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.

Dan Tao
Well reflection is not really an option (see Julien Lebosquain's answer).
chrischu
@chrischu: If you say so. It seems to me that ruling out reflection completely is a little drastic for something that only needs to be done once per class. I have used this technique many times before to write classes for me when I didn't feel like doing so much typing. So you have to restart Visual Studio afterwards. Is that so terrible?
Dan Tao
+4  A: 

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.

Julien Lebosquain
This was sweet. I adapted the template to do the same thing with generating custom type descriptors. Saved literally thousands of lines of code.
Will
+1  A: 

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"&gt;
  <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>
Scott P
Yes, I have considered using a code snippet. However using a code snippet is still way more work then using T4 (when it finally works..)I looked at PostSharp but the thing is that I just don't want to have any more dependencies in my project.
chrischu
It's true. But, in my opinion we expend a lot of energy on this pattern, just because we hate it and it's ugly. But it is not hard to understand or maintain. It takes like 5 seconds to add a property and the OnPropertyChanged boilerplate. So, what's better 2-3 lines of repetitive code or a dependency on PostSharp or some obtuse T4?
Scott P
2-3 lines of repetetive code per property. That adds up. Additionally with generated code that makes the OnPropertyChanged-calls the problem that OnPropertyChanged("SomeProperty") isn't safe for spelling errors is automatically overcome without taking a runtime performance hit (like through reflection).
chrischu
It's all a trade-off. I would offer that the lines 2-3 lines of code will be easier to maintain over the lifetime of a project. It's personal preference :-p
Scott P