José F. Romaniello

Las aventuras y desventuras de un codificador.

Introducción

Este fin de semana empecé una prueba de concepto, que luego se transformo en un proyecto. Mi idea en este artículo es mostrar todo mi razonamiento, mi metodología para enfrentar este desarrollo, como fui aprendiendo las herramientas  y como nacieron los distintos artefactos que componen esta solución.

Todo el código que voy a mostrar puede ser descargado o visto online en este sitio (HeredarPoc bitbucket). Si bien en ese repositorio llegamos a un lugar bastante avanzado, actualmente esta congelado y el código actualizado puede encontrarse aquí. No obstante, para seguir esta guía recomiendo el primer repositorio ya que parte prácticamente desde cero hasta llegar a algo bastante avanzado.

Un poco de teoría

El objetivo del proyecto es crear una herramienta que modifique nuestro ensamblado después de haber sido compilado, de manera tal que un código como este:

public class Auditable : IAuditable
{
  public DateTime Creado        { get; set; }
  public string   CreadoPor     { get; set; }
  public DateTime Modificado    { get; set; }
  public string   ModificadoPor { get; set; }
}

public class Validable : IValidable
{
  public IEnumerable<string> Validate()
  {
     ValidationService.Validate(this);
  }
}


[ExtendWith(typeof(Auditable), typeof(Validable))]
public class Persona
{
  public string Nombre   { get; set; }
  public string Apellido { get; set; }
}

Se transforme en esto, luego de compilarse:

public class Persona : IAuditable, IValidable
{
  public string   Nombre        { get; set; }
  public string   Apellido      { get; set; }

  public IEnumerable<string> Validate()
  {
     ValidationService.Validate(this);
  }

  public DateTime Creado        { get; set; }
  public string   CreadoPor     { get; set; }
  public DateTime Modificado    { get; set; }
  public string   ModificadoPor { get; set; }
}

Básicamente este es uno de los casos de uso más sencillos y utilizados de herencia múltiple; conocidos como MixIns. La idea es que cada Template (me gusta llamar a los templates “Condimento”) es un pedazo de funcionalidad mínima que podemos utilizar al programar una clase.

En este tipos de escenarios la herencia convencional falla; ya que cuando heredamos, heredamos un todo. Es probable que nuestra clase base tenga demasiadas cosas inconexas, lo cual sea difícil de mantener con el tiempo. Las cadenas de herencia largas también son difíciles de seguir, de mantener y de entender. Es por ello que una buena práctica de programación es favorecer la composición a la herencia.

Más información:

Definiendo lo que queremos

Mi primer idea fue empezar a jugar con un test mas o menos así:

using System;
using System.IO;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
using SharpTestsEx;

namespace HeredarPoc.Tests
{
    [TestFixture]
    public class MixinTests
    {
        private string weavedAssemblyPath;

        [TestFixtureSetUp]
        public void Setup()
        {
            var weaver = new Weaver();
            const string assemblytoprocessBinDebugAssemblytoprocessDll =
                @"..\..\..\AssemblyToProcess\Bin\Debug\AssemblyToProcess.dll";
            var assemblyPath = Path.GetFullPath(assemblytoprocessBinDebugAssemblytoprocessDll);
            weavedAssemblyPath = assemblyPath.Replace(".dll", "2.dll");
            weaver.Weave(assemblyPath, weavedAssemblyPath);
        }

        [Test]
        public void CanMixASimpleProperty()
        {
            var type = Assembly.LoadFile(weavedAssemblyPath).GetType("AssemblyToProcess.SampleClass1", true);
            type.Satisfy(t => t.GetProperties()
                .Any(p => p.Name == "MixedProperty" && p.PropertyType == typeof (string)));
        }

        [Test]
        public void CanUseAMixedProperty()
        {
            var assembly = Assembly.LoadFile(weavedAssemblyPath);
            dynamic instance = assembly.CreateInstance("AssemblyToProcess.SampleClass1");
            instance.MixedProperty = "hello";
            Assert.AreEqual("hello", instance.MixedProperty);
        }

    }
}

El test hace lo siguiente:

  • El setup, llama a nuestro objeto bajo estudio Weaver del cual voy a hablar en un momento, pasandole una ruta a un ensamblado original, y una ruta a un ensamblado de destino, es en esa ruta donde va a quedar nuestra dll “retocada”.
  • El método CanMixASimpleProperty carga un tipo desde el ensamblado “retocado” mediante reflection, y comprueba que ese tipo tenga la propiedad que estaba en el template.
  • El método CanUseAMixedProperty crea una instancia de este tipo, también desde el ensamblado retocado, e intentar utilizar una propiedad definida en el template. Para ello hice uso de la palabra clave “dynamic”.

El template es el siguiente:

Y la clase que use como objetivo para las pruebas es esta:

Nota sobre TDD y estilo de desarrollo

Es evidente que este tipo de tests no son “exactamente” unit tests. En este punto del desarrollo no puedo darme el lujo de escribir tests unitarios, por que apenas conozco el modelo interno de las herramientas que voy a utilizar.

Por otro lado una de las cosas mas menospreciadas de la metodología Red-Green-Refactor es la primer parte: Red. Considero que tiene mucho valor, no por el hecho de ver que este en rojo, si no que lo que mas valor me aporta es ver que la falla del test es realmente la que yo quiero.  Llegar a ese punto en este tipo de experimentos suele ser un desafío también.

Introducción a Mono.Cecil

En este punto debería quedar claro, que la intención es crear una implementación de la clase Weaver. Para ello, voy a utilizar una herramienta muy específica llamada Mono.Cecil:

Cecil is a library written by Jb Evain to generate and inspect programs and libraries in the ECMA CIL format. It has full support for generics, and support some debugging symbol format.

In simple English, with Cecil, you can load existing managed assemblies, browse all the contained types, modify them on the fly and save back to the disk the modified assembly.

Para los que están familiarizados con Reflection en .Net, es bastante sencillo de entender lo que hace. Al igual que la API de reflection, puedo obtener un tipo, ver que propiedades, métodos, fields,  sus atributos, etc. Los dos atractivos más grandes para mi caso son:

  • Permite llegar hasta niveles muchos más bajos que el api de reflection de .Net. Por ejemplo, puedo preguntar que variables están definidas dentro de un método, o cuales son las instrucciones que un método tiene.
  • Todo es modificable y puedo guardar los cambios que haga. Puedo agregar variables a un método, quitar un método de una clase, etc.

Este diagrama de clases resulta muy importante para lo que voy a explicar a continuación:

diagram

El diagrama es bastante trivial, los ensamblados en .Net contienen módulos, dichos módulos contienen tipos. TypeDefinition es de particular interés por que ahí esta todo lo que necesitamos importar, en otro TypeDefinition. Como se puede ver la mayoría son colecciones de cosas, y a todas estas colecciones se pueden agregar/quitar elementos. No son simple enumeradores.

La técnica que fui implementando es la de clonar.

Recordar que en el caso de nuestro test el Template tenía solamente un auto-property. Pero en realidad… una auto-property es un concepto de alto nivel, una forma abreviada o un truco del compilador. Al compilarse esto genera una propiedad común y corriente que internamente utiliza un backing field.

Lo que hice fue primero clonar los fields, luego clonar los métodos y luego clonar las propiedades.

De más esta decir que en cada paso hay que hacer alguna magia, para redireccionar algo que estaba apuntando a otra cosa del template. Por ejemplo, al clonar una propiedad, hay que apuntar su SetMethod y su GetMethod a los métodos ya importados.

La mayor complejidad la encontré al intentar clonar una instrucción, esto merece un título aparte.

Instrucciones en CIL

MSIL, CIL o Common Intermediate Language según wikipedia es:

…es el lenguaje de programación legible por humanos de más bajo nivel en el Common Language Infrastructure y en el .NET Framework. Los lenguajes del .NET Framework compilan a CIL. CIL es un lenguaje ensamblador orientado a objetos, y está basado en pilas. Es ejecutado por una máquina virtual. Los lenguajes .NET principales son C#, Visual Basic .NET, C++/CLI, y J#.

Me quedo con la parte, que más me gusto “lenguaje ensamblador orientado a objetos”. Me recuerda mucho a mis prácticas en la secundaria con lenguaje ensamblador, solo que ese lenguaje ensamblador no era orientado a objetos. Dentro de CIL existe todo lo que explique en el título anterior Clase, Propiedad, Método, Attributos, Variables, etc. sus modificadores de acceso y demás cosas. Pero al llegar a nivel de instrucción es cuando más se parece a assembler.

Las instrucciones en CIL, son propiamente como las de cualquier lenguaje ensamblador, están compuestas básicamente de dos partes:

  • Operador: también conocido como OpCode
  • Operando

Como el Operador (por el momento) lo puedo clonar tal y cual esta no me preocupé mucho. El operando es la parte más difícil, ¿Qué valores puede tener un operando?

  • Un operando puede apuntar a otra instrucción, un ejemplo sería en un bloque condicional, es decir un if.
  • Un operando puede apuntar a un field definido en la misma clase.
  • Un operando puede apuntar a un field definido en otra clase.
  • Un operando puede ser igual a una constante string, int, lo que sea.
  • Un operando puede apuntar a un type.

Aunque en nuestro ejemplo anterior (test) solo se presentan el caso de field y de instrucción.

Mínima implementación

Me costo bastante llegar a la mínima implementación. En el camino descubrí otras cosas:

  • Para agilizar mi trabajo utilice Reflector Pro. Esto me ayudó mucho a ver como iba quedando mi ensamblado, inclusive reflector permite alternar entre c# y código CIL. Lo cual es muy útil.
  • Mono.Cecil permite hacer cualquier cosa, por más mal que parezca. Prácticamente no realiza ninguna validación de lo que estamos haciendo y no garantiza que el runtime de .net pueda si quiera cargar nuestros ensamblados.
  • Dado lo que dije en el punto anterior, se hace imprescindible una herramienta de línea de comandos que viene con .Net llamada PeVerify.
  • El runtime de .Net es muchas veces más permisivo que PeVerify.

La primera implementación, que hace que el escenario antes mencionado funcione, es la siguiente:

using System;
using System.Collections.Generic;
using System.Linq;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Collections.Generic;

namespace HeredarPoc
{
    public class Weaver
    {
        public void Weave(string source, string target)
        {
            var assembly = AssemblyDefinition.ReadAssembly(source);
            var module = assembly.Modules.First();

            var toBeWeaved = from t in module.Types
                             let attributes =
                                 t.CustomAttributes.Where(ca => ca.AttributeType.FullName == typeof (MixInAttribute).FullName)
                             where attributes.Any()
                             select new
                                        {
                                            Type = t,
                                            MixedClasses = attributes.SelectMany(a => a.ConstructorArguments.Select(ca => ca.Value))
                                                                     .OfType<CustomAttributeArgument[]>()
                                                                     .SelectMany(caas => caas.Select(caa => caa.Value))
                                                                    .OfType<TypeReference>()
                                                                    .Select(tr => module.Types.First(td => td.FullName == tr.FullName))
                                   };
            
            foreach (var pair in toBeWeaved)
            {
                
                foreach (var fieldDefinition in pair.MixedClasses.SelectMany(mc => mc.Fields))
                {
                    var newfield = new FieldDefinition(fieldDefinition.Name, fieldDefinition.Attributes, fieldDefinition.FieldType);
                    //newfield.Attributes = fieldDefinition.Attributes;
                    foreach (var ca in fieldDefinition.CustomAttributes)
                    {
                        newfield.CustomAttributes.Add(ca);
                    }
                    pair.Type.Fields.Add(newfield);
                }

                foreach (var propertyToBeMixed in pair.MixedClasses.SelectMany(tr => tr.Properties))
                {
                    var propertyDefinition = new PropertyDefinition(propertyToBeMixed.Name, propertyToBeMixed.Attributes, propertyToBeMixed.PropertyType)
                                                 {
                                                     GetMethod = CloneMethod(propertyToBeMixed.GetMethod, pair.Type),
                                                     SetMethod = CloneMethod(propertyToBeMixed.SetMethod, pair.Type)
                                                 };
                    foreach (var customAttribute in propertyToBeMixed.CustomAttributes)
                    {
                        propertyDefinition.CustomAttributes.Add(customAttribute);
                    }
                    pair.Type.Properties.Add(propertyDefinition);
                    pair.Type.Methods.Add(propertyDefinition.GetMethod);
                    pair.Type.Methods.Add(propertyDefinition.SetMethod);
                }

                //foreach (var methodToMixIn in pair.MixedClasses.SelectMany(tr => tr.Methods))
                //{
                //    pair.Type.Methods.Add(new MethodDefinition(methodToMixIn.Name, methodToMixIn.Attributes, methodToMixIn.ReturnType)
                //                            {
                //                                Body = methodToMixIn.Body
                //                            });
                //}
            }

            module.Write(target);
        }

        private static MethodDefinition CloneMethod(MethodDefinition sourceMethod, TypeDefinition into)
        {
            var methodDefinition = new MethodDefinition(sourceMethod.Name, sourceMethod.Attributes, sourceMethod.ReturnType)
                                       {
                                           MetadataToken = MetadataToken.Zero,
                                           DeclaringType = into,
                                           Body = {InitLocals = sourceMethod.Body.InitLocals}
                                       };

            foreach (var customAttribute in sourceMethod.CustomAttributes)
            {
                methodDefinition.CustomAttributes.Add(customAttribute);
            }

            foreach (var variableDefinition in sourceMethod.Body.Variables)
            {
                methodDefinition.Body.Variables.Add(new VariableDefinition(variableDefinition.Name, variableDefinition.VariableType));
            }

            foreach (var parameterDefinition in sourceMethod.Parameters)
            {
                var definition = new ParameterDefinition(parameterDefinition.Name, parameterDefinition.Attributes, parameterDefinition.ParameterType);
                methodDefinition.Parameters.Add(definition);
            }

            var pendingInstructions = new Dictionary<int, Instruction>();

            foreach (var instruction in sourceMethod.Body.Instructions)
            {
                Instruction instructionToAdd;
                if(!pendingInstructions.TryGetValue(sourceMethod.Body.Instructions.IndexOf(instruction), out instructionToAdd))
                {
                    instructionToAdd = CloneInstruction(methodDefinition, into, sourceMethod.Body.Instructions, instruction, pendingInstructions);
                }
                methodDefinition.Body.Instructions.Add(instructionToAdd);
            }
            for (int i = 0; i < methodDefinition.Body.Instructions.Count - 1; i++)
            {
                if(methodDefinition.Body.Instructions[i] == null)
                {
                    methodDefinition.Body.Instructions.RemoveAt(i);
                }
            }
            return methodDefinition;
        }

        private static Instruction CloneInstruction(
            MethodDefinition newMethod, 
            TypeDefinition newType, 
            Collection<Instruction> sourceInstructions, 
            Instruction instructionToClone, 
            IDictionary<int, Instruction> pendingInstructions)
        {

            if (instructionToClone.Operand == null) return Instruction.Create(instructionToClone.OpCode);

            var fieldDefinition = instructionToClone.Operand as FieldDefinition;
            if(fieldDefinition != null)
            {
                return Instruction.Create(instructionToClone.OpCode, newType.Fields.First(f => f.Name == fieldDefinition.Name));
            }
            
            var instructionDefinition = instructionToClone.Operand as Instruction;
            if(instructionDefinition != null)
            {
                var offset = sourceInstructions.IndexOf(instructionDefinition);

                var targetInstruction = newMethod.Body.Instructions.ElementAtOrDefault(offset)
                                        ?? pendingInstructions.GetValueOrDefault(offset);

                if (targetInstruction == null)
                {
                    targetInstruction = CloneInstruction(newMethod, newType, sourceInstructions, instructionDefinition, pendingInstructions);
                    pendingInstructions[offset] = targetInstruction;
                }

                return Instruction.Create(instructionToClone.OpCode, targetInstruction);
            }

            throw new NotImplementedException(string.Format("can't clone instructions with operand equals to {0}", instructionToClone.Operand.GetType()));
        }
    }

    public static class DictionaryExtensions
    {
        public static TValue GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dic, TKey key)
        {
            TValue value;
            return dic.TryGetValue(key, out value) ? value : default(TValue);
        }
    }
}

Lo que esta clase hace en principio es buscar que tipos tiene que modificar y que templates debe importar en ellos. Luego para cada template va clonando pedacitos de su estructura (por ahora fields, métodos y propiedades) en la clase de destino.

El código que clona la instrucción puede parecer complejo. El problema ahí es que mi forma de clonar es secuencial, y es muy factible que el operando de una instrucción, sea otra instrucción que aún no fue clonada. Por lo tanto lo que hago es clonar inmediatamente la otra instrucción recursivamente, y almacenar en una lista para luego agregarla cuando sea su turno.

Avance y refactoring posteriores

Fui progresivamente agregando escenarios fáciles como “un método en el template que usa una propiedad del template” hasta casos mas complejos como un “template que implementa una interfaz”.

Sucesivos refactorings hicieron que el weaver me quedara de esta forma:

using System.Collections.Generic;
using System.Linq;
using HeredarPoc.Cloners;
using HeredarPoc.Inspectors;
using Mono.Cecil;

namespace HeredarPoc
{
    public class Weaver
    {
        private static readonly IEnumerable<IInspector> Inspectors
            = new IInspector[] { new AttributeBaseInspector() };

        private static readonly IEnumerable<ICloneVisitor> Visitors
            = new ICloneVisitor[]
                  {
                    new FieldCloneVisitor(), 
                    new MethodCloneVisitor(), 
                    new PropertyCloneVisitor(),
                    new InterfaceCloneVisitor()
                  };

        public void Weave(string source, string target)
        {
            var assembly = AssemblyDefinition.ReadAssembly(source);
            var pairs = Inspectors.SelectMany(i => i.GetPairs(assembly));

            foreach (var mixPair in pairs)
            {
                foreach (var mixedClass in mixPair.Templates)
                {
                    foreach (var cloneVisitor in Visitors)
                    {
                        cloneVisitor.Visit(mixedClass, mixPair.Target);
                    }
                }
            }

            assembly.MainModule.Write(target);
        }
    }
}

Como se puede ver acá, pude extraer artefactos que realizan una parte más especifica de la clonación. Como así también extraje otro artefacto que es el encargado de inspeccionar el ensamblado y encontrar que tipos debe modificar y que templates le tiene que agregar.

Inclusive el MethodCloneVisitor utiliza otros clases mas pequeñas para poder llevar a cabo el clonado del cuerpo del método. Lo cual hace que el código sea más entendible.

Plan

Simon Cropp (Australia) es quien esta trabajando conmigo en este proyecto opensource. Como dije antes, el nombre del proyecto es Heredar y se encuentra en google code. No se encuentra liberada ninguna versión todavía.

Hasta ahora soporta casos simples, nuestra idea es ir agregando progresivamente. Un buen lugar para ver los casos que están soportados son los tests, pero esta vista en el browser puede ser muy útil también.

Hemos ido importando código desde otro proyecto de Simon, del cual ya hablé antes NotifyPropertyWeaver. Esto nos permitió de manera rápida brindar soporte para muchas plataformas:

  • .Net 3.5
  • .Net 3.5 Client Profile
  • .Net 4
  • .Net 4 Client Profile
  • Silverlight 3
  • Silverlight 4
  • Silverlight on Windows Phone 7

    Absolutamente todo lo que esta soportado hasta el momento funciona en las plataformas antes mencionadas y disponemos de una suite de tests que lo verifica cada vez. También estamos utilizando integración continua con TeamCity, en el sitio de codebetters para proyectos opensource.

    Al igual que NotifyPropertyWeaver;

    • para utilizarlo solo hará falta insertar una línea que llama a una tarea de msbuild en el archivo de proyecto. (implementado)
    • no se requiere dependencias para el deployment. Dado que el weaver automáticamente eliminará el atributo y la referencia. (parcialmente implementado)

    Estamos abiertos a cualquier tipo de sugerencia o aporte, ya sea por mail, twitter o lo que sea.

    Gracias y espero no haber Sonrisa

  • | More

    This the template:

    2011-01-10_1725

    And here a little demonstration of how it works:

    Unable to display content. Adobe Flash is required.

    I love how Resharper templates can guess the expected type in these kind of templates really useful.

    | More

    This is a basic example of ISubDependencyResolver for Castle Windsor.

    Given the following service:

    public class MySuperService
    {
      public MySuperService(string connectionString)
      {
        //..
      }
    }

    I want Windsor automatically to look at the web.config or app.config settings and if there is a configuration with key equals to the parameter name (i.e. “connectionString”) to inject automatically the value when constructing MySuperService instance.

    The infrastructure code is this:

    public class DependenciesFromAppSettings : AbstractFacility
    {
        protected override void Init()
        {
            var dic = ConfigurationManager
                .AppSettings
                .AllKeys
                .ToDictionary(k => k, k => ConfigurationManager.AppSettings[k]);
    
            Kernel.Resolver.AddSubResolver(new DependenciesFromAppSettingsResolver(dic));
        }
    }
    
    public class DependenciesFromAppSettingsResolver : ISubDependencyResolver
    {
        private readonly IDictionary<string, string> webConfig;
    
        public DependenciesFromAppSettingsResolver(IDictionary<string, string> webConfig)
        {
            this.webConfig = webConfig;
        }
    
        public object Resolve(
            CreationContext context, 
            ISubDependencyResolver contextHandlerResolver, 
            ComponentModel model, 
            DependencyModel dependency)
        {
            return webConfig[dependency.DependencyKey];
        }
    
        public bool CanResolve(
            CreationContext context, 
            ISubDependencyResolver contextHandlerResolver, 
            ComponentModel model, 
            DependencyModel dependency)
        {
            return dependency.DependencyType == DependencyType.Parameter 
                && webConfig.ContainsKey(dependency.DependencyKey);
        }
    }

    And the usage is very straightforward:

    [TestFixture]
    public class DependenciesFromAppSettingsTests
    {
      [Test]
      public void CanInjectSettingsFromAppConfig()
      {
        var container = new WindsorContainer();
    
        container.AddFacility<DependenciesFromAppSettings>();
        container.Register(Component.For<MySuperService>());
    
        var superInstance = container.Resolve<MySuperService>();
        superInstance.ConnectionString
                     .Should().Be.EqualTo("sample conn string");
      } 
    }

    | More

    I found this project several days ago.

    This project uses Mono.Cecil to “weave” your assemblies after compilation.

    From the google code website you can see this example:

    //Original
    public class Person : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        public string GivenNames { get; set; }
        public string FamilyName { get; set; }
    
        public string FullName
        {
            get
            {
                return string.Format("{0} {1}", GivenNames, FamilyName);
            }
        }
    
    }
    
    //Weaved
    public class Person : INotifyPropertyChanged
    {
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        private string givenNames;
        public string GivenNames
        {
            get { return givenNames; }
            set
            {
                if (value != givenNames)
                {
                    givenNames = value;
                    OnPropertyChanged("GivenNames");
                    OnPropertyChanged("FullName");
                }
            }
        }
    
        private string familyName;
        public string FamilyName
        {
            get { return familyName; }
            set 
            {
                if (value != familyName)
                {
                    familyName = value;
                    OnPropertyChanged("FamilyName");
                    OnPropertyChanged("FullName");
                }
            }
        }
    
        public string FullName
        {
            get
            {
                return string.Format("{0} {1}", GivenNames, FamilyName);
            }
        }
    
        public void OnPropertyChanged(string propertyName)
        {
            var propertyChanged = PropertyChanged;
            if (propertyChanged != null)
            {
                propertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    Some interesting points:

    • It works on almost all versions of SL and WPF (read more).
    • It works if you already have a base class with any invoker method name (read more).
    • It has a very sophisticated algorithm to detect when it has to inject the invocation (read more).
    • It show very useful information on the output console when you build the project.
    • It doesn't need any reference to any assembly.
    • You can tell the weaver to try to inject on any members of any type. (read more)
    • You can tell explicitly in which properties you want the invocation (with an attribute and a reference that is removed after weaving).
    • You can mix implicit and explicit mode.

    I’ve been writing about automatically implementing INotifyPropertyChanged with DynamicProxy and there are other solutions like PostSharp, but I think this is the best option so far.

    Cons of the DynamicProxy version

    There are two main concerns with the DynamicProxy version:

    • WPF and Silverlight databinding infrastructure use a lot of reflection, with dynamicproxy sometimes things simply wont work. Marco Amendola wrote about some workarounds  (link link).
    • DynamicProxy versions that I saw doesn’t handle readonly scenarios like FullName or does but in a very inefficient and complex way.

    Cons of the PostSharp version

    Like the DynamicProxy version, PostSharp versions that I saw, doesn’t handle readonly scenarios like FullName. I started a thread in the “Sharing Ideas” forum a on July of 2010, because I thought this was an important (and possible) feature to the framework, but the answer was that it wasn’t possible (read the full thread here surprisingly enough I gave they the same example that is in the NotifyPropertyWeaver main page). Gael Fraiteur said about an alternative approach, like having a code as follows:

    public class Person
    {
      [NotifyPropertyChanged(Dependants = "FullName")]
      public string FirstName{get;set;}
    
      [NotifyPropertyChanged(Dependants = "FullName")]
      public string LastName{get;set;}
    
      public string FullName
      {
        get{ return FirstName + " " + LastName; }
      }
    }

    I’ve never used it, and I’ve never used PostSharp for INPC, because I don’t see any advantage compared the standard way in such cases.

    Because PostSharp is a general purpose AOP framework and it can do a lot of things more than the NotifyPropertyWeaver, the weaved code may look unfamiliar and very complex. I don’t think it is inefficient or slow code, and it is not an issue for me, but I hear other people to say that.

    Finally

    Don’t get me wrong, PostSharp and DynamicProxy are both great frameworks and they are useful for lot of other things. I really like PostSharp as an AOP framework, using Mono.Cecil to modify your assemblies is not something than mortals like me can do on daily work, but I can implement a PostSharp aspect with few lines of code.

    However my advice for INotifyPropertyChanged is to use this framework.

    Kudos for Simon Cropp for the amazing work and for be willing to hear opinions about his project. I started to tweet about his project, and he immediately added me to twitter and gtalk. This says a lot about him and his commitment as an OSS project lead.

    | More

    El 1 de enero de 2011 amanecí en la ciudad de Tinogasta, Catamarca con una fuerte acides estomacal debido a los festejos de año nuevo. Habíamos viajado a esta ciudad para pasar el año nuevo con la familia de mi esposa.

    A las 13 horas revisé mi correo desde el teléfono, y ¡oh sorpresa!:

    Estimado/a Jose Romaniello,
    Enhorabuena. Nos complace presentarle el programa de nombramiento MVP de Microsoft® de 2011. Este nombramiento se concede a los líderes excepcionales de la comunidad técnica que comparten de forma activa su experiencia de alta calidad y de la vida real con otras personas. Le agradecemos especialmente la contribución que ha realizado en las comunidades técnicas en el área de Visual C# a lo largo del pasado año.

    En Microsoft creemos que las comunidades técnicas mejoran la vida de las personas y el éxito del sector gracias a que expertos independientes, como usted, ayudan a otros a obtener un mayor valor de los productos y las tecnologías mediante el intercambio gratuito y objetivo de conocimientos. Como MVP de Microsoft forma parte de un grupo muy selecto de expertos que representan lo mejor y lo más brillante de la tecnología y que comparten un compromiso profundo con la comunidad y la voluntad de ayudar a los demás.

    El mail es mas largo e incluye otros detalles pero esta es la parte que quería compartir con mis lectores.

    Me emocioné bastante al leerlo, es realmente muy alentador que Microsoft reconozca el esfuerzo que muchas personas hacen en las comunidades técnicas y les dé esta mención.

    Como siempre digo yo soy de esas personas que tienen la suerte de disfrutar plenamente de su trabajo, todos los días aprendo algo nuevo y todos los días quiero aprender más. Desde hace dos años he sumado el hecho de compartirlo, en el blog, twitter, lista de correos, webcasts, etc.

    Ayer recibí un paquete con varias chucherías muy lindas:

    100_1832

    Agradecimientos

    Por sobre todas las cosas quisiera agradecer a mi esposa Nadia, y a mi hija Valentina, quienes siempre me apoyan incondicionalmente y siempre tuvieron paciencia con mis actividades extra-laborales. Sin el amor, el cariño y la contención de ellas dos, no podría haber pasado tantas horas estudiando, debatiendo, aprendiendo y hasta enseñando.

    También quería agradecerle a mi mamá Franca y a mi papá Gínes que siempre creyeron en mi. Si de chico les hice algunos “format c:” no era mi intención que perdieran cosas importantes, o todo un día de facturación, era simplemente yo experimentando y descubriendo.

    No podría dejar de agradecer a todos los miembros de Alt Net Hispano, ya que en 2009 y 2010 he crecido mucho profesionalmente debido a los debates que tenemos todos los días en la lista de correo y también en las reuniones online que tenemos semanalmente. En especial un agradecimiento muy profundo a mi amigo Jorge Gamba, quien día a día hace posible que esta comunidad funcione como tal. Él realmente es un líder extraordinario, él mejor que podríamos tener.

    Otro agradecimiento especial para mi amigo Fabio Maulo, con él he compartido largas charlas y fue una de las personas que me empujo hacia Alt.Net y al mundo Open Source en general.

    Como así también quisiera agradecer a Jason Dentler por dejarme participar en la revisión técnica de su libro NHibernate Cookbok, editorial Packt Publishing. Ha sido una buena experiencia, de la cual he aprendido mucho.

    Por último a algunos MVPs; Cristian Prieto, Pablo Cibraro y Jorge Gamba quienes me nominaron como candidato a MVP. A Microsoft y a los miembros del programa Microsoft MVP, en especial al líder de Latinoamérica Fernando García Lorea.

    Nuevos compromisos

    Los compromisos que me gustaría asumir en este 2011 son los siguientes:

    • Seguir aprendiendo, y seguir compartiendo en los mismos lugares donde lo he venido haciendo hasta el momento, por que esos ya son mis lugares y es allí donde mas cómodo me siento.
    • Involucrarme más con el futuro de C# y .Net en general. Intentar que mi voz y las de mis compañeros se escuchen más.
    • Involucrarme más en la comunidad local de Alt Net Argentina. Después del último openspace que tuvimos (se puede encontrar un buen resumen en el blog de Pedro Wood), me lleve una muy buena sensación, me parece que hay gente muy capaz con muchas buenas ideas.
    • Probar el césped del vecino, caminarlo un rato, etc. Mucha gente del mundo de .Net con la que comparto largas charlas esta probando otros horizontes y hasta migrando. Tal es el caso de tecnologías como Ruby on Rails, Ruby en general o Python. Creo que como el título dice “expertos independientes”, tenemos que siempre estar buscando como hacer mejor nuestro trabajo. También, la profesión así lo demanda, no alcanza con lo que uno aprende en la facultad, ni alcanza con lo que uno aprendió la semana anterior.

    Muchas gracias! Y buen año!

    | More