Last time we took a step towards adding useful functionality to C# types to automatically add some of the features you get for free in functional languages, such as immutability and equality comparisons based on properties.

This time we’ll look at how to use PostSharp to inject the functionality we’re interested in based on custom attributes. Not only will that be more efficient at runtime than relying on .NET reflection, it will allow us to derive our types from others if we wish.

PostSharp immutable threading pattern

As I was trying to write the code for this article, I came across this article: New in PostSharp 3.2: Immutable and Freezable Done Well. This discusses some new attributes available only in recent versions of PostSharp, so you need to install it via NuGet with the “Include Prerelease” option selected. You also need to install the pre-release version of the PostSharp Threading Pattern Library.

This means you can define the value class with a simple [Immutable] attribute like this:

using PostSharp.Patterns.Threading;

namespace ValueClasses
{
    [Immutable]
    public class Item
    {
        public int Id { get; private set; }
        public string Description { get; private set; }

        public Item(int id, string description)
        {
            Id = id;
            Description = description;
        }

        public void ChangeDescription()
        {
            Description = "changed";
        }
    }
}

And then if we run a program like this:

using System;
using ValueClasses;

namespace DDD2
{
    class Program
    {
        static void Main()
        {
            var item = new Item(123, "My Description");
            Console.WriteLine(item);
            item.ChangeDescription();
            Console.WriteLine(item);
        }
    }
}

… then we get an exception thrown from the ChangeDescription call.

That’s pretty awesome! PostSharp is preventing the class’s own method from changing its property’s backing field after construction. That’s a level of protection (and bug prevention) you can’t get through private setters.

Decompiling the resultant assembly with JetBrains’ free stand-alone tool dotPeek shows just how much extra code PostSharp has injected to get this to work:

using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Aspects.Internals;
using PostSharp.Extensibility;
using PostSharp.Patterns.Model;
using PostSharp.Patterns.Threading;
using PostSharp.Reflection;
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace ValueClasses
{
  [HasInheritedAttribute(new long[] {})]
  [Immutable(AttributeId = 881225518374429271L, AttributePriority = 268435456)]
  [HasOnInstanceInitialized]
  public class Item : IAttachable, IImmutable
  {
    private int \u003CId\u003Ek__OriginalField;
    private string \u003CDescription\u003Ek__OriginalField;
    [NonSerialized]
    private ImmutableAttribute \u003C\u003Ez__aspect0;
    [NonSerialized]
    private AggregatableAttribute \u003C\u003Ez__aspect1;

    public int Id
    {
      get
      {
        return this.get_\u003CId\u003Ek__BackingField();
      }
      private set
      {
        this.set_\u003CId\u003Ek__BackingField(value);
      }
    }

    public string Description
    {
      get
      {
        return this.get_\u003CDescription\u003Ek__BackingField();
      }
      private set
      {
        this.set_\u003CDescription\u003Ek__BackingField(value);
      }
    }

    [CompilerGenerated]
    private int \u003CId\u003Ek__BackingField
    {
      [DebuggerNonUserCode] get
      {
        LocationInterceptionArgsImpl<int> interceptionArgsImpl = new LocationInterceptionArgsImpl<int>((object) this, Arguments.Empty);
        interceptionArgsImpl.DeclarationIdentifier = new DeclarationIdentifier(881225515594350592L);
        interceptionArgsImpl.TypedBinding = (LocationBinding<int>) Item.\u003CId\u003Ec__Binding0.singleton;
        this.\u003C\u003Ez__aspect0.OnFieldGet((LocationInterceptionArgs) interceptionArgsImpl);
        return interceptionArgsImpl.TypedValue;
      }
      [DebuggerNonUserCode] set
      {
        LocationInterceptionArgsImpl<int> interceptionArgsImpl = new LocationInterceptionArgsImpl<int>((object) this, Arguments.Empty);
        interceptionArgsImpl.DeclarationIdentifier = new DeclarationIdentifier(881225515594350592L);
        interceptionArgsImpl.TypedBinding = (LocationBinding<int>) Item.\u003CId\u003Ec__Binding.singleton;
        interceptionArgsImpl.TypedValue = value;
        this.\u003C\u003Ez__aspect0.OnFieldSet((LocationInterceptionArgs) interceptionArgsImpl);
      }
    }

    [CompilerGenerated]
    private string \u003CDescription\u003Ek__BackingField
    {
      [DebuggerNonUserCode] get
      {
        LocationInterceptionArgsImpl<string> interceptionArgsImpl = new LocationInterceptionArgsImpl<string>((object) this, Arguments.Empty);
        interceptionArgsImpl.DeclarationIdentifier = new DeclarationIdentifier(881225515594350593L);
        interceptionArgsImpl.TypedBinding = (LocationBinding<string>) Item.\u003CDescription\u003Ec__Binding0.singleton;
        this.\u003C\u003Ez__aspect0.OnFieldGet((LocationInterceptionArgs) interceptionArgsImpl);
        return interceptionArgsImpl.TypedValue;
      }
      [DebuggerNonUserCode] set
      {
        LocationInterceptionArgsImpl<string> interceptionArgsImpl = new LocationInterceptionArgsImpl<string>((object) this, Arguments.Empty);
        interceptionArgsImpl.DeclarationIdentifier = new DeclarationIdentifier(881225515594350593L);
        interceptionArgsImpl.TypedBinding = (LocationBinding<string>) Item.\u003CDescription\u003Ec__Binding.singleton;
        interceptionArgsImpl.TypedValue = value;
        this.\u003C\u003Ez__aspect0.OnFieldSet((LocationInterceptionArgs) interceptionArgsImpl);
      }
    }

    protected Item(int id, string description, ConstructorDepth __depth)
    {
      // ISSUE: reference to a compiler-generated method
      this.\u003C\u003Ez__InitializeAspects(AspectInitializationReason.Constructor);
      this.Id = id;
      this.Description = description;
      if (!__depth.IsZero)
        return;
      // ISSUE: reference to a compiler-generated method
      this.OnInstanceInitialized();
    }

    public void ChangeDescription()
    {
      this.Description = "changed";
    }

    protected virtual void OnChildDetached(object child, ChildInfo childInfo)
    {
      this.\u003C\u003Ez__aspect1.OnChildDetached(child, childInfo);
    }

    protected virtual void OnParentChanged()
    {
      this.\u003C\u003Ez__aspect0.OnParentChanged();
    }

    protected virtual void OnChildAttached(object child, ChildInfo childInfo)
    {
      this.\u003C\u003Ez__aspect0.OnChildAttached(child, childInfo);
    }

    protected virtual void OnAncestorChanged(AncestorChangedEventArgs args)
    {
      this.\u003C\u003Ez__aspect1.OnAncestorChanged(args);
    }

    protected virtual bool VisitChildren(ChildVisitor visitor, ChildVisitorOptions options)
    {
      return this.\u003C\u003Ez__aspect1.VisitChildren(visitor, options);
    }

    private void \u003COnChildAttached\u003Ez__(object child, ChildInfo childInfo)
    {
      this.\u003C\u003Ez__aspect1.OnChildAttached(child, childInfo);
    }

    private void \u003COnParentChanged\u003Ez__()
    {
      this.\u003C\u003Ez__aspect1.OnParentChanged();
    }
  }
}

So, yeah… you wouldn’t really want to write that lot by hand to achieve the same thing.

Other aspects

As well as the [Immutable] aspect, there’s also a [Freezable] model. This is similar but you start with a fully mutable object that you can make changes to and at some point you call its Freeze() method and after that you can no longer change it.

Adding Equals and GetHashCode

So that’s a summary of the type of thing PostSharp can do. What about adding Equals and GetHashCode like the previous article?

I couldn’t work out how to do this myself so I asked the question on Stack Overflow.

Here’s some code based on the answer I got there:

using System;
using System.Collections.Generic;
using System.Reflection;
using PostSharp.Aspects;
using PostSharp.Aspects.Advices;
using PostSharp.Reflection;
using PostSharp.Serialization;

namespace ValueClasses
{
    [PSerializable]
    class AutoEqAttribute : InstanceLevelAspect, IAdviceProvider
    {
        /// <summary>
        /// The fields from the class this attribute has been added to
        /// </summary>
        /// <remarks>
        /// Automatically populated by PostSharp with the fields identified by
        /// <see cref="ProvideAdvices"/>
        /// </remarks>
        public List<ILocationBinding> Fields;

        /////////
        // Equals

        // Grab a reference to the base class's Equals method

        [ImportMember("Equals", IsRequired = true, Order = ImportMemberOrder.BeforeIntroductions)]
        public Func<object, bool> EqualsBaseMethod;

        // Add a new Equals method to the class which this attribute has been added to

        [IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail)]
        public new bool Equals(object other)
        {
            // TODO: Define a smarter way to determine if base.Equals should be invoked.
            if (EqualsBaseMethod.Method.DeclaringType != typeof(object))
            {
                if (!EqualsBaseMethod(other))
                    return false;
            }

            object instance = Instance;
            foreach (ILocationBinding binding in Fields)
            {
                // The following code is inefficient because it boxes all fields.
                // There is currently no workaround.
                object thisFieldValue = binding.GetValue(ref instance, Arguments.Empty);
                object otherFieldValue = binding.GetValue(ref other, Arguments.Empty);

                if (!object.Equals(thisFieldValue, otherFieldValue))
                    return false;
            }

            return true;
        }

        //////////////
        // GetHashCode

        [IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.OverrideOrFail)]
        public new int GetHashCode()
        {
            object instance = Instance;

            unchecked
            {
                int hashcode = 11;
                foreach (ILocationBinding binding in Fields)
                {
                    object thisFieldValue = binding.GetValue(ref instance, Arguments.Empty);
                    int thisHash = thisFieldValue == null ? 0 : thisFieldValue.GetHashCode();
                    hashcode = (hashcode * 397) ^ thisHash;
                }

                return hashcode;
            }
        }

        /// <summary>
        /// Provide advices for setting up <see cref="Fields"/>
        /// </summary>
        /// <param name="targetElement">The type to which this attribute has been added</param>
        /// <returns>A sequence of advice instances</returns>
        public IEnumerable<AdviceInstance> ProvideAdvices(object targetElement)
        {
            var targetType = (Type)targetElement;
            FieldInfo bindingField = GetType().GetField("Fields");

            const BindingFlags bindingFlags =
                BindingFlags.DeclaredOnly |
                BindingFlags.Instance |
                BindingFlags.Public |
                BindingFlags.NonPublic;

            FieldInfo[] fields = targetType.GetFields(bindingFlags);

            foreach (FieldInfo field in fields)
            {
                yield return new ImportLocationAdviceInstance(bindingField, new LocationInfo(field));
            }
        }
    }
}

Then you can just add a [AutoEq] attribute to your simple class and get Equals and GetHashCode automatically implemented.

Other options

T4 templates

While writing this article I read this Stack Overflow question that suggests doing the same thing by using T4 templates (specifically the T4 C# Constructor Generator project) to add this functionality via partial classes.

Resharper

In this particular example, perhaps the most pragmatic option is to use Resharper’s menu option Resharper | Edit | Generate Code | Equality Members. You can just pick which fields and properties should contribute, say whether you want to implement IEquatable<T> as well, and leave Resharper to generate the boilerplate code for you.

All three options are worth knowing about and have their strengths and weaknesses. Postsharp is good for adding cross-cutting aspects to your code, logging being the canonical example. T4 templates look to be useful for automatically generating textual content, including source code, but aren’t widely used. Finally, the fact that Resharper could do the heavy lifting all along is a reminder that it’s always worth investing the time to understand the capabilities of the tools you already have before you start looking further afield.