With.Extensions Travis build status AppVeayor build status

Extension methods used to copy and update immutable classes (as copy and update record expression in F#).

Why ?

When using immutables classes with C#, it becomes really annoying to copy and update an object. To do that, you have 2 options :

The second solution makes your code more readable but you have to create methods for each field you want to modify in your class, and it can be a lot of work…

This project has been created to supply extensions to duplicate easily your immutable classes in C# (of course if you have the choice, you should use F#…)

Usage

  var source = Tuple.Create("first value", "second value", "third value");

  // If you have multiple fields to update
  var updated = source
    .With(obj => obj.Item1, "new first value")
    .With(obj => obj.Item2, "new second value")
    .Create(); 

  // Or if you have a single field to update
  var updated2 = source.CopyWith(obj => obj.Item1, "new first value");

Chaining

Calling With extension will cause all future method calls to return wrapped query objects. When you’ve finished, call Create() to get the final value.

  var source = Tuple.Create(1, 2, 3);

  // Only create a query object
  var query = source
    .With(obj => obj.Item1, 2)
    .With(obj => obj.Item2, 4);

  // Execute the query to create a new object
  var updated = query.Create();

How does it work ?

For a given immutable class, the extension search for actual values to use as parameters in the constructor (by using parameter’s name).

Restrictions

To use the extension, your immutable class must define a unique constructor.

Naming conventions

By default, name of a constructor argument must match the name of a corresponding field/property (using pascal case convention). For example, if a constructor argument is named ‘defaultValue’, extension will search for a field/property named ‘DefaultValue’.

When calling Create, you can override default behavior by providing your own name converter. For example, if you use ‘m_’ prefixes like below :

  public class Immutable
  {
    public readonly string m_FirstField;
    public readonly string m_SecondField;
    public Immutable(string firstField, string secondField)
    {
      this.m_FirstField = firstField;
      this.m_SecondField = secondField;
    }
  }

  ...

  var instance = new Immutable("first value", "second value");
  var updated = instance
      .With(obj => obj.m_FirstField, "new first value")
      .Create(name => "m_" + Naming.PascalCase.Convert(name));

Providers

2 providers are available to configure the way new objects are created :

  // Provider used to create new instances
  // Provides constructor method for a given Type
  // ConstructorInfo -> Constructor
  With.WithExtensions.ConstructorProvider = ...

  // Provider used to get property/field values on an instance
  // Type -> (propertyOrFieldName : string) -> PropertyOrFieldAccessor
  With.WithExtensions.AccessorProvider = ...

This assembly includes 2 kinds of providers :

Used in combination with memoization, it creates compiled expressions to create new instances. Memoization uses a ConcurrentDictionary internally to store compiled expressions. Performances are much more better than with pure reflection, at the cost of compilation time the first time a class is duplicated (and a little memory overhead).

Expression providers are configured by default as below :

WithExtensions.ConstructorProvider = Cache.Memoize<ConstructorInfo, Constructor>(ExpressionProviders.BuildConstructor);
WithExtensions.AccessorProvider = Cache.Memoize<Type, string, PropertyOrFieldAccessor>(ExpressionProviders.BuildPropertyOrFieldAccessor);

Providers using pure reflection to create new instances. You can use this provider by making these changes at your application startup :

WithExtensions.ConstructorProvider = ReflectionProviders.GetConstructor;
WithExtensions.AccessorProvider = ReflectionProviders.GetPropertyOrFieldAccessor;

Download

NuGet package can be downloaded here.