Skip to content

ehorrent/With.Extensions

Repository files navigation

With.Extensions Travis build status AppVeayor build status NuGet 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 :

  • use the constructor (verbose and add 'noise' on what you really want to do)
  • create manually copy methods to duplicate your objects

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 :

  • ExpressionProviders (used by default)

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);
  • ReflectionProviders

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.

About

Extension methods used to copy and update immutable classes

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published