Extension methods used to copy and update immutable classes (as copy and update record expression in F#).
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#...)
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");
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();
For a given immutable class, the extension search for actual values to use as parameters in the constructor (by using parameter's name).
To use the extension, your immutable class must define a unique constructor.
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));
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;
NuGet package can be downloaded here.