Автоматическая реализация INotifyPropertyChanged через генерацию кода T4? - PullRequest
10 голосов
/ 03 июня 2010

В настоящее время я работаю над созданием нового проекта, и мне было интересно, как мне добиться того, чтобы мои классы ViewModel имели поддержку INotifyPropertyChanged, при этом не нужно вручную кодировать все свойства.

Я изучил AOP-фреймворки, но думаю, что они просто взорвут мой проект другой зависимостью.

Итак, я подумал о генерации реализаций свойств с помощью T4.

Настройка будет такой: у меня есть класс ViewModel, который объявляет только его переменные фона свойств, а затем я использую T4 для генерации реализаций свойств из него.

Например, это будет моя ViewModel:

public partial class ViewModel
{
    private string p_SomeProperty;
}

Тогда T4 перебирает исходный файл, ищет объявления членов с именем «p_» и генерирует файл, подобный этому:

public partial class ViewModel
{
    public string SomeProperty
    {
        get
        {
            return p_SomeProperty;
        }
        set
        {
            p_SomeProperty= value;
            NotifyPropertyChanged("SomeProperty");
        }
    }
}

У этого подхода есть некоторые преимущества, но я не уверен, что он действительно может работать. Поэтому я хотел опубликовать свою идею здесь, в StackOverflow, в качестве вопроса, чтобы получить некоторую обратную связь и, возможно, несколько советов, как это можно сделать лучше / проще / безопаснее.

Ответы [ 3 ]

7 голосов
/ 03 июня 2010

Вот отличная статья Колина Эберхардта о создании свойств зависимости от T4 путем проверки пользовательских атрибутов непосредственно из Visual Studio с EnvDTE. Не должно быть проблем с его адаптацией для проверки полей и генерации кода соответствующим образом, так как публикация содержит простые служебные методы для просмотра узлов кода.

Обратите внимание, что при использовании T4 от VS вы не должны использовать Reflection на своих собственных сборках, иначе они будут заблокированы, и вам придется перезапустить Visual Studio, чтобы восстановить.

3 голосов
/ 03 июня 2010

Есть много способов снять шкуру с этой кошки.

Мы играли с PostSharp, чтобы внедрить шаблон INotifyProperty. Кажется, это работает довольно хорошо.

При этом нет никаких причин, по которым Т4 не будет работать.

Я согласен с Дэном в том, что вам следует создать реализацию базового класса OnPropertyChanged.

Рассматривали ли вы просто использование фрагмента кода? Он напишет шаблон для вас. Единственным недостатком является то, что он не будет обновляться автоматически, если вы захотите изменить имя свойства на более поздний срок.

<?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>
1 голос
/ 03 июня 2010

Это определенно должно работать.

Я бы рекомендовал сначала написать базовый класс, который реализует INotifyPropertyChanged, предоставив ему метод protected void OnPropertyChanged(string propertyName), заставив его кэшировать свои PropertyChangeEventArgs объекты (по одному на уникальное имя свойства - нет смысла создавать новый объект каждый время возникновения события), и ваш сгенерированный T4 класс наследуется от этой базы.

Чтобы получить членов, которым нужны свойства, вы можете просто сделать что-то вроде:

BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic;
FieldInfo[] fieldsNeedingProperties = inputType.GetFields(flags)
    .Where(f => f.Name.StartsWith("p_"))
    .ToArray();

И оттуда:

<# 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);
    }
#>

и т. Д.

...