Open nzbart opened 7 years ago
If you're interested, I've written a feature I'm using to allow access to deep properties. Sample tests:
using FluentAssertions;
using NUnit.Framework;
using With;
class SampleOuterOuter
{
public SampleOuterOuter(int value, SampleOuterOuter recursiveInner)
{
Value = value;
RecursiveInner = recursiveInner;
}
public int Value { get; }
public SampleOuterOuter RecursiveInner { get; }
}
class SampleOuter
{
public SampleOuter(int value, SampleInner inner)
{
Value = value;
Inner = inner;
}
public int Value { get; }
public SampleInner Inner { get; }
}
class SampleInner
{
public SampleInner(string text)
{
Text = text;
}
public string Text { get; }
}
public class WithDepthExtensionsTests
{
[Test]
public void Given_an_object_with_a_nested_object_When_updating_the_nested_object_Then_the_original_is_unchanged_And_the_new_object_is_correct()
{
var value = new SampleOuter(42, new SampleInner("Hello, World!"));
var modified = value.CopyWithDeep(v => v.Inner, v => v.Text, "Changed");
value.Value.Should().Be(42);
value.Inner.Text.Should().Be("Hello, World!");
modified.Value.Should().Be(42);
modified.Inner.Text.Should().Be("Changed");
}
[Test]
public void Given_a_deeply_nested_object_When_updating_the_deepest_object_Then_the_original_is_unchanged_And_the_new_object_is_correct()
{
var value = new SampleOuterOuter(1, new SampleOuterOuter(2, new SampleOuterOuter(3, new SampleOuterOuter(4, null))));
var modified = value.CopyWithDeep(v => v.RecursiveInner, v => v.RecursiveInner, v => v.RecursiveInner, v => v.Value, 1000);
value.Value.Should().Be(1);
value.RecursiveInner.RecursiveInner.RecursiveInner.Value.Should().Be(4);
modified.Value.Should().Be(1);
modified.RecursiveInner.RecursiveInner.RecursiveInner.Value.Should().Be(1000);
}
[Test]
public void Given_an_object_with_a_nested_object_When_updating_the_outer_object_Then_the_value_is_changed_And_the_original_reference_to_the_inner_object_is_unchanged()
{
var value = new SampleOuter(42, new SampleInner("Hello, World!"));
var modified = value.CopyWith(v => v.Value, 21);
value.Value.Should().Be(42);
modified.Value.Should().Be(21);
modified.Inner.Should().BeSameAs(value.Inner);
}
}
I had to codegen the implementation:
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
using System;
using System.Linq.Expressions;
using With;
public static class WithDepthExtensions
{
public static TSource CopyWithDeep<TSource, TPropertyOrField>(
this TSource source,
Expression<Func<TSource, TPropertyOrField>> propertyOrFieldSelector,
TPropertyOrField value)
where TSource : class
{
return source.With(propertyOrFieldSelector, value).Create();
}
<# Func<int, string> BuildTemplateParameters = (int number) => string.Join(", ", Enumerable.Range(1, number).Select(n => "TPropertyOrField" + n)); #>
<# Func<int, string> BuildMethodParameters = (int number) => string.Join("\r\n ", Enumerable.Range(1, number).Select(n => "Expression<Func<TPropertyOrField" + n + ", TPropertyOrField" + (n + 1) + ">> propertyOrFieldSelector" + (n + 1) + ",")); #>
<# Func<int, string> BuildWhereClause = (int number) => string.Join("\r\n ", Enumerable.Range(1, number).Select(n => "where TPropertyOrField" + n + ": class")); #>
<# Func<int, string> BuildRecursionArguments = (int number) => string.Join(", ", Enumerable.Range(2, number).Select(n => "propertyOrFieldSelector" + n)); #>
<# for(int i = 2; i <= 10; ++i) { #>
public static TSource CopyWithDeep<TSource, <#= BuildTemplateParameters(i) #>>(
this TSource source,
Expression<Func<TSource, TPropertyOrField1>> propertyOrFieldSelector1,
<#= BuildMethodParameters(i - 1) #>
TPropertyOrField<#= i #> value)
where TSource : class
<#= BuildWhereClause(i - 1) #>
{
var innerValue = propertyOrFieldSelector1.Compile().Invoke(source).CopyWithDeep(<#= BuildRecursionArguments(i - 1) #>, value);
return source.CopyWithDeep(propertyOrFieldSelector1, innerValue);
}
<# } #>
}
If you're interested, I could submit this code as a pull request. I'm also looking at supporting modification of collections as in my original comment. The code and tests can be simplified and tidied before I submit it - this is mostly a proof of concept.
Another update: I have some code that will allow manipulation of collections in addition to the deep manipulation code above. I've got this in my codebase for now, but am willing to submit a pull request if you're interested.
Hi, sorry for the late response (holidays here). Yes i'm interested, you can submit a pull request !
Look into what can be reused from: https://github.com/wallymathieu/with
For instance: https://github.com/wallymathieu/with/blob/master/src/Tests/With/Manipulation_of_readonly_dictionary.cs
Would it be possible to support more complex scenarios, such as collections?
For example, could I "modify" one of the values in
Wrapper.Collection
?