2010-06-03 8 views
10

Actualmente estoy trabajando en la configuración de un nuevo proyecto mío y me preguntaba cómo podría lograr que mis clases de ViewModel tengan soporte INotifyPropertyChanged sin tener que codificar todas las propiedades yo mismo.Implementación automática de INotifyPropertyChanged a través de la generación de código T4?

Miré en los marcos de trabajo de AOP, pero creo que simplemente harían estallar mi proyecto con otra dependencia.

Así que pensé en generar las implementaciones de propiedad con T4.

La configuración sería esta: tengo una clase ViewModel que declara solo sus variables de fondo de Propiedades y luego uso T4 para generar las implementaciones de propiedades de la misma.

Por ejemplo, este sería mi modelo de vista:

public partial class ViewModel 
{ 
    private string p_SomeProperty; 
} 

Entonces T4 iría sobre el archivo de origen y buscar declaraciones de miembros nombrados "P_" y generar un archivo de la siguiente manera:

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

Este enfoque tiene algunas ventajas, pero no estoy seguro si realmente puede funcionar. Así que quería publicar mi idea aquí en StackOverflow como una pregunta para obtener algunos comentarios sobre ella y tal vez algunos consejos sobre cómo se puede hacer mejor/más fácil/más seguro.

+2

Me siento como un idiota, pero no tenía idea de lo que es T4 hasta que lo busqué en Google por esta pregunta. ¡No puedo creer que esto no se hable más! – BFree

+0

Yo también. Llegué aquí del hilo del preprocesador C# de un año (http://stackoverflow.com/questions/986404/does-a-c-preprocessing-tool-exist). Doh. – lo5

+0

vea también http://stackoverflow.com/questions/1315621/implementing-inotifypropertychanged-does-a-better-way-exist –

Respuesta

7

Here's a great post by Colin Eberhardt en la generación de Propiedades de dependencia desde un T4 mediante la inspección de atributos personalizados directamente desde Visual Studio con EnvDTE. No debería ser difícil adaptarlo para inspeccionar los campos y generar código de manera apropiada, ya que la publicación contiene métodos simples de utilidad para explorar los nodos de código.

Tenga en cuenta que al usar T4 desde VS, no debe usar Reflection en sus propios ensamblajes o se bloquearán y tendrá que reiniciar Visual Studio para poder reconstruir.

+0

Esto fue dulce. Adapte la plantilla para hacer lo mismo con la generación de descripciones de tipos personalizados. Guardado literalmente miles de líneas de código. – Will

+1

Tenga en cuenta que a partir de VS2010 SP1, el problema de bloqueo se resuelve, por lo que la reflexión ahora está bien para su uso. – GarethJ

1

Definitivamente debería funcionar.

te recomiendo primero a escribir una clase base que implementa INotifyPropertyChanged, dándole un método protected void OnPropertyChanged(string propertyName), por lo que es almacenar en caché sus PropertyChangeEventArgs objetos (uno por cada nombre de propiedad única - no tiene sentido en la creación de un nuevo objeto cada vez que el evento se produce), y su clase generada por T4 deriva de esta base.

para obtener los miembros que necesitan propiedades implementadas, que sólo puede hacer algo como:

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

Y a partir de ahí:

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

Y así sucesivamente.

+0

La reflexión del pozo no es realmente una opción (ver la respuesta de Julien Lebosquain). – chrischu

+1

@chrischu: si tú lo dices. Me parece que descartar la reflexión por completo es un poco drástico para algo que solo debe hacerse una vez por clase. He usado esta técnica muchas veces antes para escribir clases para mí cuando no tenía ganas de hacer tantas mecanografías. Entonces debes reiniciar Visual Studio luego. ¿Es eso tan terrible? –

+1

Tenga en cuenta que a partir de VS2010 SP1, el problema de bloqueo se resuelve. – GarethJ

3

Hay muchas maneras de despellejar a este gato.

Hemos estado jugando con PostSharp para inyectar la plantilla de INotifyProperty. Eso parece funcionar bastante bien.

Dicho esto, no hay ninguna razón por la que T4 no funcione.

Estoy de acuerdo con Dan en que debe crear la implementación de la clase base de OnPropertyChanged.

¿Ha considerado solo usar un fragmento de código?Escribirá el texto estándar para usted. La única desventaja es que no se actualizará automáticamente si desea cambiar el nombre de la propiedad más adelante.

<?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> 
+0

Sí, he considerado utilizar un fragmento de código. Sin embargo, usar un fragmento de código todavía es mucho más trabajo que usar T4 (cuando finalmente funciona ...) Miré PostSharp, pero la cuestión es que simplemente no quiero tener más dependencias en mi proyecto. – chrischu

+0

Es cierto. Pero, en mi opinión, gastamos mucha energía en este patrón, solo porque lo odiamos y es feo. Pero no es difícil de entender o mantener. Tarda unos 5 segundos en agregar una propiedad y el texto repetido OnPropertyChanged. Entonces, ¿qué es mejor 2-3 líneas de código repetitivo o una dependencia de PostSharp o algún T4 obtuso? –

+0

2-3 líneas de código repetitivo por propiedad. Eso suma. Además, con el código generado que hace que OnPropertyChanged-Calls el problema que OnPropertyChanged ("SomeProperty") no es seguro para los errores de ortografía se supere automáticamente sin tomar un golpe de rendimiento en tiempo de ejecución (como a través de la reflexión). – chrischu

Cuestiones relacionadas