Immutable type updater using a special constructor












4














I wanted to update some properties of my UriString but since this type is immutable, it wouldn't work. Actually, I just wanted to update the Path property but I don't want to have a constructor with several parameters like in the other question because this is too much work and sometime not desireable. Instead, I require the immutable type to have an immutable-update-construtor that takes the ImmutableUpdate object that I use for this purpose.



To the user it looks like a dummy class without any useful properties:



public sealed class ImmutableUpdate : IEnumerable<(PropertyInfo Property, object Value)>
{
private readonly IEnumerable<(PropertyInfo Property, object Value)> _updates;

internal ImmutableUpdate(IEnumerable<(PropertyInfo Property, object Value)> updates)
{
_updates = updates;
}

public IEnumerator<(PropertyInfo Property, object Value)> GetEnumerator() => _updates.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => _updates.GetEnumerator();
}


and it should, because it's only a carrier type for updates and an alias for the lengthy enumeration signature. These updates are created and used by the ImmutableUpdater that binds values to properties via their backing-fields and allows the Bind method to be called only from within the constructor of the object being updated. This should be a simple protection against mutating random instances. ImmutableUpdate has also an internal constructor and is sealed which also prevents using it in a wrong way. (ImmutableUpdater currently looks for properties that have no setter but extending it to understand some special attributes to customize this procees should be possible.)



public static class ImmutableUpdater
{
public static T With<T, TMember>(this T obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
if (!(memberSelector.Body is MemberExpression memberExpression))
{
throw new ArgumentException($"You must select a member. Affected expression '{memberSelector}'.");
}

if (!(memberExpression.Member is PropertyInfo selectedProperty))
{
throw new ArgumentException($"You must select a property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.GetSetMethod() != null)
{
throw new ArgumentException($"You must select a readonly property. Affected expression '{memberSelector}'.");
}

if (GetBackingField<T>(selectedProperty.Name) == null)
{
throw new ArgumentException($"You must select a pure readonly property (not a computed one). Affected expression '{memberSelector}'.");
}

var immmutableUpdateCtor =
typeof(T)
.GetConstructor(new { typeof(ImmutableUpdate) });

var updates =
from property in obj.ImmutableProperties()
let getsUpdated = property.Name == selectedProperty.Name
select
(
property,
getsUpdated ? newValue : property.GetValue(obj)
);

return (T)Activator.CreateInstance(typeof(T), new ImmutableUpdate(updates));
}

public static void Bind<T>(this ImmutableUpdate update, T obj)
{
// todo - this could be cached
var isCalledByImmutableUpdateCtor = new StackFrame(1).GetMethod() == ImmutableUpdateConstructor(typeof(T));
if (!isCalledByImmutableUpdateCtor)
{
throw new InvalidOperationException($"You can call '{nameof(Bind)}' only from within an ImmutableUpdate constructor.");
}

foreach (var (property, value) in update)
{
GetBackingField<T>(property.Name)?.SetValue(obj, value);
}
}

private static FieldInfo GetBackingField<T>(string propertyName)
{
var backingFieldBindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var backingFieldName = $"<{propertyName}>k__BackingField";
return typeof(T).GetField(backingFieldName, backingFieldBindingFlags);
}

private static IEnumerable<PropertyInfo> ImmutableProperties<T>(this T obj)
{
return
typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetSetMethod() is null);
}

private static ConstructorInfo ImmutableUpdateConstructor(Type type)
{
return type.GetConstructor(new { typeof(ImmutableUpdate) });
}
}


Example



Its usage is pretty simple, just use With to set a new value.



var user = new User();

var updatedUser = user
.With(x => x.FirstName, "John")
.With(x => x.LastName, "Doe")
//.With(x => x.FullName, "Doe") // Boom!
.Dump();

user.Dump();


This is the type using Bind inside its special constructor:



class User
{
public User() { }

public User(ImmutableUpdate update)
{
update.Bind(this);
}

public string FirstName { get; }

public string LastName { get; }
}




Is this solution any better than others, or worse? What do you say? I'm not really concerned about performance as this won't be used for any crazy scenarios (yet).










share|improve this question




















  • 1




    I was literally just contemplating writing one of these... I was wondering how best to specify properties, and had completely forgotten about Expressions, so thanks ;)
    – VisualMelon
    Dec 20 at 15:34






  • 1




    At first blush, the chained Withs that return a new object each time seem a little wasteful if the next one is discarding the previous. Perhaps it could return an "interim" container object that can accumulate the With expressions and a final Apply() that executes them once and produces a single User (in this case) object?
    – Jesse C. Slicer
    Dec 20 at 17:16
















4














I wanted to update some properties of my UriString but since this type is immutable, it wouldn't work. Actually, I just wanted to update the Path property but I don't want to have a constructor with several parameters like in the other question because this is too much work and sometime not desireable. Instead, I require the immutable type to have an immutable-update-construtor that takes the ImmutableUpdate object that I use for this purpose.



To the user it looks like a dummy class without any useful properties:



public sealed class ImmutableUpdate : IEnumerable<(PropertyInfo Property, object Value)>
{
private readonly IEnumerable<(PropertyInfo Property, object Value)> _updates;

internal ImmutableUpdate(IEnumerable<(PropertyInfo Property, object Value)> updates)
{
_updates = updates;
}

public IEnumerator<(PropertyInfo Property, object Value)> GetEnumerator() => _updates.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => _updates.GetEnumerator();
}


and it should, because it's only a carrier type for updates and an alias for the lengthy enumeration signature. These updates are created and used by the ImmutableUpdater that binds values to properties via their backing-fields and allows the Bind method to be called only from within the constructor of the object being updated. This should be a simple protection against mutating random instances. ImmutableUpdate has also an internal constructor and is sealed which also prevents using it in a wrong way. (ImmutableUpdater currently looks for properties that have no setter but extending it to understand some special attributes to customize this procees should be possible.)



public static class ImmutableUpdater
{
public static T With<T, TMember>(this T obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
if (!(memberSelector.Body is MemberExpression memberExpression))
{
throw new ArgumentException($"You must select a member. Affected expression '{memberSelector}'.");
}

if (!(memberExpression.Member is PropertyInfo selectedProperty))
{
throw new ArgumentException($"You must select a property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.GetSetMethod() != null)
{
throw new ArgumentException($"You must select a readonly property. Affected expression '{memberSelector}'.");
}

if (GetBackingField<T>(selectedProperty.Name) == null)
{
throw new ArgumentException($"You must select a pure readonly property (not a computed one). Affected expression '{memberSelector}'.");
}

var immmutableUpdateCtor =
typeof(T)
.GetConstructor(new { typeof(ImmutableUpdate) });

var updates =
from property in obj.ImmutableProperties()
let getsUpdated = property.Name == selectedProperty.Name
select
(
property,
getsUpdated ? newValue : property.GetValue(obj)
);

return (T)Activator.CreateInstance(typeof(T), new ImmutableUpdate(updates));
}

public static void Bind<T>(this ImmutableUpdate update, T obj)
{
// todo - this could be cached
var isCalledByImmutableUpdateCtor = new StackFrame(1).GetMethod() == ImmutableUpdateConstructor(typeof(T));
if (!isCalledByImmutableUpdateCtor)
{
throw new InvalidOperationException($"You can call '{nameof(Bind)}' only from within an ImmutableUpdate constructor.");
}

foreach (var (property, value) in update)
{
GetBackingField<T>(property.Name)?.SetValue(obj, value);
}
}

private static FieldInfo GetBackingField<T>(string propertyName)
{
var backingFieldBindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var backingFieldName = $"<{propertyName}>k__BackingField";
return typeof(T).GetField(backingFieldName, backingFieldBindingFlags);
}

private static IEnumerable<PropertyInfo> ImmutableProperties<T>(this T obj)
{
return
typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetSetMethod() is null);
}

private static ConstructorInfo ImmutableUpdateConstructor(Type type)
{
return type.GetConstructor(new { typeof(ImmutableUpdate) });
}
}


Example



Its usage is pretty simple, just use With to set a new value.



var user = new User();

var updatedUser = user
.With(x => x.FirstName, "John")
.With(x => x.LastName, "Doe")
//.With(x => x.FullName, "Doe") // Boom!
.Dump();

user.Dump();


This is the type using Bind inside its special constructor:



class User
{
public User() { }

public User(ImmutableUpdate update)
{
update.Bind(this);
}

public string FirstName { get; }

public string LastName { get; }
}




Is this solution any better than others, or worse? What do you say? I'm not really concerned about performance as this won't be used for any crazy scenarios (yet).










share|improve this question




















  • 1




    I was literally just contemplating writing one of these... I was wondering how best to specify properties, and had completely forgotten about Expressions, so thanks ;)
    – VisualMelon
    Dec 20 at 15:34






  • 1




    At first blush, the chained Withs that return a new object each time seem a little wasteful if the next one is discarding the previous. Perhaps it could return an "interim" container object that can accumulate the With expressions and a final Apply() that executes them once and produces a single User (in this case) object?
    – Jesse C. Slicer
    Dec 20 at 17:16














4












4








4







I wanted to update some properties of my UriString but since this type is immutable, it wouldn't work. Actually, I just wanted to update the Path property but I don't want to have a constructor with several parameters like in the other question because this is too much work and sometime not desireable. Instead, I require the immutable type to have an immutable-update-construtor that takes the ImmutableUpdate object that I use for this purpose.



To the user it looks like a dummy class without any useful properties:



public sealed class ImmutableUpdate : IEnumerable<(PropertyInfo Property, object Value)>
{
private readonly IEnumerable<(PropertyInfo Property, object Value)> _updates;

internal ImmutableUpdate(IEnumerable<(PropertyInfo Property, object Value)> updates)
{
_updates = updates;
}

public IEnumerator<(PropertyInfo Property, object Value)> GetEnumerator() => _updates.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => _updates.GetEnumerator();
}


and it should, because it's only a carrier type for updates and an alias for the lengthy enumeration signature. These updates are created and used by the ImmutableUpdater that binds values to properties via their backing-fields and allows the Bind method to be called only from within the constructor of the object being updated. This should be a simple protection against mutating random instances. ImmutableUpdate has also an internal constructor and is sealed which also prevents using it in a wrong way. (ImmutableUpdater currently looks for properties that have no setter but extending it to understand some special attributes to customize this procees should be possible.)



public static class ImmutableUpdater
{
public static T With<T, TMember>(this T obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
if (!(memberSelector.Body is MemberExpression memberExpression))
{
throw new ArgumentException($"You must select a member. Affected expression '{memberSelector}'.");
}

if (!(memberExpression.Member is PropertyInfo selectedProperty))
{
throw new ArgumentException($"You must select a property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.GetSetMethod() != null)
{
throw new ArgumentException($"You must select a readonly property. Affected expression '{memberSelector}'.");
}

if (GetBackingField<T>(selectedProperty.Name) == null)
{
throw new ArgumentException($"You must select a pure readonly property (not a computed one). Affected expression '{memberSelector}'.");
}

var immmutableUpdateCtor =
typeof(T)
.GetConstructor(new { typeof(ImmutableUpdate) });

var updates =
from property in obj.ImmutableProperties()
let getsUpdated = property.Name == selectedProperty.Name
select
(
property,
getsUpdated ? newValue : property.GetValue(obj)
);

return (T)Activator.CreateInstance(typeof(T), new ImmutableUpdate(updates));
}

public static void Bind<T>(this ImmutableUpdate update, T obj)
{
// todo - this could be cached
var isCalledByImmutableUpdateCtor = new StackFrame(1).GetMethod() == ImmutableUpdateConstructor(typeof(T));
if (!isCalledByImmutableUpdateCtor)
{
throw new InvalidOperationException($"You can call '{nameof(Bind)}' only from within an ImmutableUpdate constructor.");
}

foreach (var (property, value) in update)
{
GetBackingField<T>(property.Name)?.SetValue(obj, value);
}
}

private static FieldInfo GetBackingField<T>(string propertyName)
{
var backingFieldBindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var backingFieldName = $"<{propertyName}>k__BackingField";
return typeof(T).GetField(backingFieldName, backingFieldBindingFlags);
}

private static IEnumerable<PropertyInfo> ImmutableProperties<T>(this T obj)
{
return
typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetSetMethod() is null);
}

private static ConstructorInfo ImmutableUpdateConstructor(Type type)
{
return type.GetConstructor(new { typeof(ImmutableUpdate) });
}
}


Example



Its usage is pretty simple, just use With to set a new value.



var user = new User();

var updatedUser = user
.With(x => x.FirstName, "John")
.With(x => x.LastName, "Doe")
//.With(x => x.FullName, "Doe") // Boom!
.Dump();

user.Dump();


This is the type using Bind inside its special constructor:



class User
{
public User() { }

public User(ImmutableUpdate update)
{
update.Bind(this);
}

public string FirstName { get; }

public string LastName { get; }
}




Is this solution any better than others, or worse? What do you say? I'm not really concerned about performance as this won't be used for any crazy scenarios (yet).










share|improve this question















I wanted to update some properties of my UriString but since this type is immutable, it wouldn't work. Actually, I just wanted to update the Path property but I don't want to have a constructor with several parameters like in the other question because this is too much work and sometime not desireable. Instead, I require the immutable type to have an immutable-update-construtor that takes the ImmutableUpdate object that I use for this purpose.



To the user it looks like a dummy class without any useful properties:



public sealed class ImmutableUpdate : IEnumerable<(PropertyInfo Property, object Value)>
{
private readonly IEnumerable<(PropertyInfo Property, object Value)> _updates;

internal ImmutableUpdate(IEnumerable<(PropertyInfo Property, object Value)> updates)
{
_updates = updates;
}

public IEnumerator<(PropertyInfo Property, object Value)> GetEnumerator() => _updates.GetEnumerator();

IEnumerator IEnumerable.GetEnumerator() => _updates.GetEnumerator();
}


and it should, because it's only a carrier type for updates and an alias for the lengthy enumeration signature. These updates are created and used by the ImmutableUpdater that binds values to properties via their backing-fields and allows the Bind method to be called only from within the constructor of the object being updated. This should be a simple protection against mutating random instances. ImmutableUpdate has also an internal constructor and is sealed which also prevents using it in a wrong way. (ImmutableUpdater currently looks for properties that have no setter but extending it to understand some special attributes to customize this procees should be possible.)



public static class ImmutableUpdater
{
public static T With<T, TMember>(this T obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
if (!(memberSelector.Body is MemberExpression memberExpression))
{
throw new ArgumentException($"You must select a member. Affected expression '{memberSelector}'.");
}

if (!(memberExpression.Member is PropertyInfo selectedProperty))
{
throw new ArgumentException($"You must select a property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.GetSetMethod() != null)
{
throw new ArgumentException($"You must select a readonly property. Affected expression '{memberSelector}'.");
}

if (GetBackingField<T>(selectedProperty.Name) == null)
{
throw new ArgumentException($"You must select a pure readonly property (not a computed one). Affected expression '{memberSelector}'.");
}

var immmutableUpdateCtor =
typeof(T)
.GetConstructor(new { typeof(ImmutableUpdate) });

var updates =
from property in obj.ImmutableProperties()
let getsUpdated = property.Name == selectedProperty.Name
select
(
property,
getsUpdated ? newValue : property.GetValue(obj)
);

return (T)Activator.CreateInstance(typeof(T), new ImmutableUpdate(updates));
}

public static void Bind<T>(this ImmutableUpdate update, T obj)
{
// todo - this could be cached
var isCalledByImmutableUpdateCtor = new StackFrame(1).GetMethod() == ImmutableUpdateConstructor(typeof(T));
if (!isCalledByImmutableUpdateCtor)
{
throw new InvalidOperationException($"You can call '{nameof(Bind)}' only from within an ImmutableUpdate constructor.");
}

foreach (var (property, value) in update)
{
GetBackingField<T>(property.Name)?.SetValue(obj, value);
}
}

private static FieldInfo GetBackingField<T>(string propertyName)
{
var backingFieldBindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;
var backingFieldName = $"<{propertyName}>k__BackingField";
return typeof(T).GetField(backingFieldName, backingFieldBindingFlags);
}

private static IEnumerable<PropertyInfo> ImmutableProperties<T>(this T obj)
{
return
typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetSetMethod() is null);
}

private static ConstructorInfo ImmutableUpdateConstructor(Type type)
{
return type.GetConstructor(new { typeof(ImmutableUpdate) });
}
}


Example



Its usage is pretty simple, just use With to set a new value.



var user = new User();

var updatedUser = user
.With(x => x.FirstName, "John")
.With(x => x.LastName, "Doe")
//.With(x => x.FullName, "Doe") // Boom!
.Dump();

user.Dump();


This is the type using Bind inside its special constructor:



class User
{
public User() { }

public User(ImmutableUpdate update)
{
update.Bind(this);
}

public string FirstName { get; }

public string LastName { get; }
}




Is this solution any better than others, or worse? What do you say? I'm not really concerned about performance as this won't be used for any crazy scenarios (yet).







c# reflection extension-methods immutability






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Dec 20 at 15:44









Heslacher

44.8k460155




44.8k460155










asked Dec 20 at 15:31









t3chb0t

34k746112




34k746112








  • 1




    I was literally just contemplating writing one of these... I was wondering how best to specify properties, and had completely forgotten about Expressions, so thanks ;)
    – VisualMelon
    Dec 20 at 15:34






  • 1




    At first blush, the chained Withs that return a new object each time seem a little wasteful if the next one is discarding the previous. Perhaps it could return an "interim" container object that can accumulate the With expressions and a final Apply() that executes them once and produces a single User (in this case) object?
    – Jesse C. Slicer
    Dec 20 at 17:16














  • 1




    I was literally just contemplating writing one of these... I was wondering how best to specify properties, and had completely forgotten about Expressions, so thanks ;)
    – VisualMelon
    Dec 20 at 15:34






  • 1




    At first blush, the chained Withs that return a new object each time seem a little wasteful if the next one is discarding the previous. Perhaps it could return an "interim" container object that can accumulate the With expressions and a final Apply() that executes them once and produces a single User (in this case) object?
    – Jesse C. Slicer
    Dec 20 at 17:16








1




1




I was literally just contemplating writing one of these... I was wondering how best to specify properties, and had completely forgotten about Expressions, so thanks ;)
– VisualMelon
Dec 20 at 15:34




I was literally just contemplating writing one of these... I was wondering how best to specify properties, and had completely forgotten about Expressions, so thanks ;)
– VisualMelon
Dec 20 at 15:34




1




1




At first blush, the chained Withs that return a new object each time seem a little wasteful if the next one is discarding the previous. Perhaps it could return an "interim" container object that can accumulate the With expressions and a final Apply() that executes them once and produces a single User (in this case) object?
– Jesse C. Slicer
Dec 20 at 17:16




At first blush, the chained Withs that return a new object each time seem a little wasteful if the next one is discarding the previous. Perhaps it could return an "interim" container object that can accumulate the With expressions and a final Apply() that executes them once and produces a single User (in this case) object?
– Jesse C. Slicer
Dec 20 at 17:16










1 Answer
1






active

oldest

votes


















2














A couple of small things here. My bigger thought is in the comment on the original question.



One:



Empty collections are nice if nulls show their ugly faces:



internal ImmutableUpdate(IEnumerable<(PropertyInfo Property, object Value)> updates)
{
_updates = updates ?? Enumerable.Empty<(PropertyInfo Property, object Value)>();
}


Two:



    var immmutableUpdateCtor =
typeof(T)
.GetConstructor(new { typeof(ImmutableUpdate) });


is never used. I'd rewrite that little block as:



var immmutableUpdateCtor = ImmutableUpdateConstructor(typeof(T));

if (immutableUpdateCtor == null)
{
return obj;
}

var updates =
from property in obj.ImmutableProperties()
let getsUpdated = property.Name == selectedProperty.Name
select
(
property,
getsUpdated ? newValue : property.GetValue(obj)
);

return (T)immutableUpdateCtor.Invoke(new object { new ImmutableUpdate(updates) });


I'll addend more if I think of anything.



Three:



The caching of constructor information as was commented in the code:



    private static readonly ConcurrentDictionary<Type, ConstructorInfo> _ImmutableConstructors =
new ConcurrentDictionary<Type, ConstructorInfo>();

private static ConstructorInfo ImmutableUpdateConstructor(Type type)
{
if (!_ImmutableConstructors.TryGetValue(type, out var constructor))
{
constructor = type.GetConstructor(new { typeof(ImmutableUpdate) });
_ImmutableConstructors.TryAdd(type, constructor);
}

return constructor;
}


Four:



Here are the builder pieces:



In ImmutableUpdater class:



public static UpdateBuilder<T> With<T, TMember>(this T obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
ConstructorInfo immutableUpdateCtor = ImmutableUpdateConstructor(typeof(T));

return new UpdateBuilder<T>(obj, immutableUpdateCtor).With(memberSelector, newValue);
}

public static UpdateBuilder<T> With<T, TMember>(this UpdateBuilder<T> obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
if (!(memberSelector.Body is MemberExpression memberExpression))
{
throw new ArgumentException($"You must select a member. Affected expression '{memberSelector}'.");
}

if (!(memberExpression.Member is PropertyInfo selectedProperty))
{
throw new ArgumentException($"You must select a property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.GetSetMethod() != null)
{
throw new ArgumentException(
$"You must select a readonly property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.Name.GetBackingField<T>() == null)
{
throw new ArgumentException(
$"You must select a pure readonly property (not a computed one). Affected expression '{memberSelector}'.");
}

var updates =
from property in obj.ImmutableProperties()
where property.Name == selectedProperty.Name
select
(
property, (object)newValue
);

return obj.Add(updates);
}

private static IEnumerable<PropertyInfo> ImmutableProperties<T>(this UpdateBuilder<T> obj)
{
return
typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(propertyInfo => propertyInfo.GetSetMethod() is null);
}


and finally, the UpdateBuilder<T> class:



using System.Linq;
using System.Reflection;

using IEnumerablePropertyValue = System.Collections.Generic.IEnumerable<(System.Reflection.PropertyInfo Property, object Value)>;
using PropertyValueList = System.Collections.Generic.List<(System.Reflection.PropertyInfo Property, object Value)>;

public sealed class UpdateBuilder<T>
{
private readonly PropertyValueList _updates = new PropertyValueList();

private readonly ConstructorInfo _immutableUpdateCtor;

public UpdateBuilder(T obj, ConstructorInfo immutableUpdateCtor)
{
this.Object = obj;
this._immutableUpdateCtor = immutableUpdateCtor;
}

public T Object { get; }

public UpdateBuilder<T> Add(IEnumerablePropertyValue updates)
{
foreach (var update in updates ?? Enumerable.Empty<(PropertyInfo Property, object Value)>())
{
this._updates.Add(update);
}

return this;
}

public static implicit operator T(UpdateBuilder<T> updateBuilder)
{
if (updateBuilder == null)
{
return default(T);
}

if (updateBuilder._immutableUpdateCtor == null)
{
return updateBuilder.Object;
}

return (T)updateBuilder._immutableUpdateCtor.Invoke(new object { new ImmutableUpdate(updateBuilder._updates) });
}





share|improve this answer



















  • 1




    Ooops, it stayed there after moving it do the helper ;-]
    – t3chb0t
    Dec 20 at 17:35






  • 1




    You must like it ;-) I see you've added a builder. I'll definitely borrow it.
    – t3chb0t
    Dec 20 at 20:21











Your Answer





StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");

StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210056%2fimmutable-type-updater-using-a-special-constructor%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









2














A couple of small things here. My bigger thought is in the comment on the original question.



One:



Empty collections are nice if nulls show their ugly faces:



internal ImmutableUpdate(IEnumerable<(PropertyInfo Property, object Value)> updates)
{
_updates = updates ?? Enumerable.Empty<(PropertyInfo Property, object Value)>();
}


Two:



    var immmutableUpdateCtor =
typeof(T)
.GetConstructor(new { typeof(ImmutableUpdate) });


is never used. I'd rewrite that little block as:



var immmutableUpdateCtor = ImmutableUpdateConstructor(typeof(T));

if (immutableUpdateCtor == null)
{
return obj;
}

var updates =
from property in obj.ImmutableProperties()
let getsUpdated = property.Name == selectedProperty.Name
select
(
property,
getsUpdated ? newValue : property.GetValue(obj)
);

return (T)immutableUpdateCtor.Invoke(new object { new ImmutableUpdate(updates) });


I'll addend more if I think of anything.



Three:



The caching of constructor information as was commented in the code:



    private static readonly ConcurrentDictionary<Type, ConstructorInfo> _ImmutableConstructors =
new ConcurrentDictionary<Type, ConstructorInfo>();

private static ConstructorInfo ImmutableUpdateConstructor(Type type)
{
if (!_ImmutableConstructors.TryGetValue(type, out var constructor))
{
constructor = type.GetConstructor(new { typeof(ImmutableUpdate) });
_ImmutableConstructors.TryAdd(type, constructor);
}

return constructor;
}


Four:



Here are the builder pieces:



In ImmutableUpdater class:



public static UpdateBuilder<T> With<T, TMember>(this T obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
ConstructorInfo immutableUpdateCtor = ImmutableUpdateConstructor(typeof(T));

return new UpdateBuilder<T>(obj, immutableUpdateCtor).With(memberSelector, newValue);
}

public static UpdateBuilder<T> With<T, TMember>(this UpdateBuilder<T> obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
if (!(memberSelector.Body is MemberExpression memberExpression))
{
throw new ArgumentException($"You must select a member. Affected expression '{memberSelector}'.");
}

if (!(memberExpression.Member is PropertyInfo selectedProperty))
{
throw new ArgumentException($"You must select a property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.GetSetMethod() != null)
{
throw new ArgumentException(
$"You must select a readonly property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.Name.GetBackingField<T>() == null)
{
throw new ArgumentException(
$"You must select a pure readonly property (not a computed one). Affected expression '{memberSelector}'.");
}

var updates =
from property in obj.ImmutableProperties()
where property.Name == selectedProperty.Name
select
(
property, (object)newValue
);

return obj.Add(updates);
}

private static IEnumerable<PropertyInfo> ImmutableProperties<T>(this UpdateBuilder<T> obj)
{
return
typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(propertyInfo => propertyInfo.GetSetMethod() is null);
}


and finally, the UpdateBuilder<T> class:



using System.Linq;
using System.Reflection;

using IEnumerablePropertyValue = System.Collections.Generic.IEnumerable<(System.Reflection.PropertyInfo Property, object Value)>;
using PropertyValueList = System.Collections.Generic.List<(System.Reflection.PropertyInfo Property, object Value)>;

public sealed class UpdateBuilder<T>
{
private readonly PropertyValueList _updates = new PropertyValueList();

private readonly ConstructorInfo _immutableUpdateCtor;

public UpdateBuilder(T obj, ConstructorInfo immutableUpdateCtor)
{
this.Object = obj;
this._immutableUpdateCtor = immutableUpdateCtor;
}

public T Object { get; }

public UpdateBuilder<T> Add(IEnumerablePropertyValue updates)
{
foreach (var update in updates ?? Enumerable.Empty<(PropertyInfo Property, object Value)>())
{
this._updates.Add(update);
}

return this;
}

public static implicit operator T(UpdateBuilder<T> updateBuilder)
{
if (updateBuilder == null)
{
return default(T);
}

if (updateBuilder._immutableUpdateCtor == null)
{
return updateBuilder.Object;
}

return (T)updateBuilder._immutableUpdateCtor.Invoke(new object { new ImmutableUpdate(updateBuilder._updates) });
}





share|improve this answer



















  • 1




    Ooops, it stayed there after moving it do the helper ;-]
    – t3chb0t
    Dec 20 at 17:35






  • 1




    You must like it ;-) I see you've added a builder. I'll definitely borrow it.
    – t3chb0t
    Dec 20 at 20:21
















2














A couple of small things here. My bigger thought is in the comment on the original question.



One:



Empty collections are nice if nulls show their ugly faces:



internal ImmutableUpdate(IEnumerable<(PropertyInfo Property, object Value)> updates)
{
_updates = updates ?? Enumerable.Empty<(PropertyInfo Property, object Value)>();
}


Two:



    var immmutableUpdateCtor =
typeof(T)
.GetConstructor(new { typeof(ImmutableUpdate) });


is never used. I'd rewrite that little block as:



var immmutableUpdateCtor = ImmutableUpdateConstructor(typeof(T));

if (immutableUpdateCtor == null)
{
return obj;
}

var updates =
from property in obj.ImmutableProperties()
let getsUpdated = property.Name == selectedProperty.Name
select
(
property,
getsUpdated ? newValue : property.GetValue(obj)
);

return (T)immutableUpdateCtor.Invoke(new object { new ImmutableUpdate(updates) });


I'll addend more if I think of anything.



Three:



The caching of constructor information as was commented in the code:



    private static readonly ConcurrentDictionary<Type, ConstructorInfo> _ImmutableConstructors =
new ConcurrentDictionary<Type, ConstructorInfo>();

private static ConstructorInfo ImmutableUpdateConstructor(Type type)
{
if (!_ImmutableConstructors.TryGetValue(type, out var constructor))
{
constructor = type.GetConstructor(new { typeof(ImmutableUpdate) });
_ImmutableConstructors.TryAdd(type, constructor);
}

return constructor;
}


Four:



Here are the builder pieces:



In ImmutableUpdater class:



public static UpdateBuilder<T> With<T, TMember>(this T obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
ConstructorInfo immutableUpdateCtor = ImmutableUpdateConstructor(typeof(T));

return new UpdateBuilder<T>(obj, immutableUpdateCtor).With(memberSelector, newValue);
}

public static UpdateBuilder<T> With<T, TMember>(this UpdateBuilder<T> obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
if (!(memberSelector.Body is MemberExpression memberExpression))
{
throw new ArgumentException($"You must select a member. Affected expression '{memberSelector}'.");
}

if (!(memberExpression.Member is PropertyInfo selectedProperty))
{
throw new ArgumentException($"You must select a property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.GetSetMethod() != null)
{
throw new ArgumentException(
$"You must select a readonly property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.Name.GetBackingField<T>() == null)
{
throw new ArgumentException(
$"You must select a pure readonly property (not a computed one). Affected expression '{memberSelector}'.");
}

var updates =
from property in obj.ImmutableProperties()
where property.Name == selectedProperty.Name
select
(
property, (object)newValue
);

return obj.Add(updates);
}

private static IEnumerable<PropertyInfo> ImmutableProperties<T>(this UpdateBuilder<T> obj)
{
return
typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(propertyInfo => propertyInfo.GetSetMethod() is null);
}


and finally, the UpdateBuilder<T> class:



using System.Linq;
using System.Reflection;

using IEnumerablePropertyValue = System.Collections.Generic.IEnumerable<(System.Reflection.PropertyInfo Property, object Value)>;
using PropertyValueList = System.Collections.Generic.List<(System.Reflection.PropertyInfo Property, object Value)>;

public sealed class UpdateBuilder<T>
{
private readonly PropertyValueList _updates = new PropertyValueList();

private readonly ConstructorInfo _immutableUpdateCtor;

public UpdateBuilder(T obj, ConstructorInfo immutableUpdateCtor)
{
this.Object = obj;
this._immutableUpdateCtor = immutableUpdateCtor;
}

public T Object { get; }

public UpdateBuilder<T> Add(IEnumerablePropertyValue updates)
{
foreach (var update in updates ?? Enumerable.Empty<(PropertyInfo Property, object Value)>())
{
this._updates.Add(update);
}

return this;
}

public static implicit operator T(UpdateBuilder<T> updateBuilder)
{
if (updateBuilder == null)
{
return default(T);
}

if (updateBuilder._immutableUpdateCtor == null)
{
return updateBuilder.Object;
}

return (T)updateBuilder._immutableUpdateCtor.Invoke(new object { new ImmutableUpdate(updateBuilder._updates) });
}





share|improve this answer



















  • 1




    Ooops, it stayed there after moving it do the helper ;-]
    – t3chb0t
    Dec 20 at 17:35






  • 1




    You must like it ;-) I see you've added a builder. I'll definitely borrow it.
    – t3chb0t
    Dec 20 at 20:21














2












2








2






A couple of small things here. My bigger thought is in the comment on the original question.



One:



Empty collections are nice if nulls show their ugly faces:



internal ImmutableUpdate(IEnumerable<(PropertyInfo Property, object Value)> updates)
{
_updates = updates ?? Enumerable.Empty<(PropertyInfo Property, object Value)>();
}


Two:



    var immmutableUpdateCtor =
typeof(T)
.GetConstructor(new { typeof(ImmutableUpdate) });


is never used. I'd rewrite that little block as:



var immmutableUpdateCtor = ImmutableUpdateConstructor(typeof(T));

if (immutableUpdateCtor == null)
{
return obj;
}

var updates =
from property in obj.ImmutableProperties()
let getsUpdated = property.Name == selectedProperty.Name
select
(
property,
getsUpdated ? newValue : property.GetValue(obj)
);

return (T)immutableUpdateCtor.Invoke(new object { new ImmutableUpdate(updates) });


I'll addend more if I think of anything.



Three:



The caching of constructor information as was commented in the code:



    private static readonly ConcurrentDictionary<Type, ConstructorInfo> _ImmutableConstructors =
new ConcurrentDictionary<Type, ConstructorInfo>();

private static ConstructorInfo ImmutableUpdateConstructor(Type type)
{
if (!_ImmutableConstructors.TryGetValue(type, out var constructor))
{
constructor = type.GetConstructor(new { typeof(ImmutableUpdate) });
_ImmutableConstructors.TryAdd(type, constructor);
}

return constructor;
}


Four:



Here are the builder pieces:



In ImmutableUpdater class:



public static UpdateBuilder<T> With<T, TMember>(this T obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
ConstructorInfo immutableUpdateCtor = ImmutableUpdateConstructor(typeof(T));

return new UpdateBuilder<T>(obj, immutableUpdateCtor).With(memberSelector, newValue);
}

public static UpdateBuilder<T> With<T, TMember>(this UpdateBuilder<T> obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
if (!(memberSelector.Body is MemberExpression memberExpression))
{
throw new ArgumentException($"You must select a member. Affected expression '{memberSelector}'.");
}

if (!(memberExpression.Member is PropertyInfo selectedProperty))
{
throw new ArgumentException($"You must select a property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.GetSetMethod() != null)
{
throw new ArgumentException(
$"You must select a readonly property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.Name.GetBackingField<T>() == null)
{
throw new ArgumentException(
$"You must select a pure readonly property (not a computed one). Affected expression '{memberSelector}'.");
}

var updates =
from property in obj.ImmutableProperties()
where property.Name == selectedProperty.Name
select
(
property, (object)newValue
);

return obj.Add(updates);
}

private static IEnumerable<PropertyInfo> ImmutableProperties<T>(this UpdateBuilder<T> obj)
{
return
typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(propertyInfo => propertyInfo.GetSetMethod() is null);
}


and finally, the UpdateBuilder<T> class:



using System.Linq;
using System.Reflection;

using IEnumerablePropertyValue = System.Collections.Generic.IEnumerable<(System.Reflection.PropertyInfo Property, object Value)>;
using PropertyValueList = System.Collections.Generic.List<(System.Reflection.PropertyInfo Property, object Value)>;

public sealed class UpdateBuilder<T>
{
private readonly PropertyValueList _updates = new PropertyValueList();

private readonly ConstructorInfo _immutableUpdateCtor;

public UpdateBuilder(T obj, ConstructorInfo immutableUpdateCtor)
{
this.Object = obj;
this._immutableUpdateCtor = immutableUpdateCtor;
}

public T Object { get; }

public UpdateBuilder<T> Add(IEnumerablePropertyValue updates)
{
foreach (var update in updates ?? Enumerable.Empty<(PropertyInfo Property, object Value)>())
{
this._updates.Add(update);
}

return this;
}

public static implicit operator T(UpdateBuilder<T> updateBuilder)
{
if (updateBuilder == null)
{
return default(T);
}

if (updateBuilder._immutableUpdateCtor == null)
{
return updateBuilder.Object;
}

return (T)updateBuilder._immutableUpdateCtor.Invoke(new object { new ImmutableUpdate(updateBuilder._updates) });
}





share|improve this answer














A couple of small things here. My bigger thought is in the comment on the original question.



One:



Empty collections are nice if nulls show their ugly faces:



internal ImmutableUpdate(IEnumerable<(PropertyInfo Property, object Value)> updates)
{
_updates = updates ?? Enumerable.Empty<(PropertyInfo Property, object Value)>();
}


Two:



    var immmutableUpdateCtor =
typeof(T)
.GetConstructor(new { typeof(ImmutableUpdate) });


is never used. I'd rewrite that little block as:



var immmutableUpdateCtor = ImmutableUpdateConstructor(typeof(T));

if (immutableUpdateCtor == null)
{
return obj;
}

var updates =
from property in obj.ImmutableProperties()
let getsUpdated = property.Name == selectedProperty.Name
select
(
property,
getsUpdated ? newValue : property.GetValue(obj)
);

return (T)immutableUpdateCtor.Invoke(new object { new ImmutableUpdate(updates) });


I'll addend more if I think of anything.



Three:



The caching of constructor information as was commented in the code:



    private static readonly ConcurrentDictionary<Type, ConstructorInfo> _ImmutableConstructors =
new ConcurrentDictionary<Type, ConstructorInfo>();

private static ConstructorInfo ImmutableUpdateConstructor(Type type)
{
if (!_ImmutableConstructors.TryGetValue(type, out var constructor))
{
constructor = type.GetConstructor(new { typeof(ImmutableUpdate) });
_ImmutableConstructors.TryAdd(type, constructor);
}

return constructor;
}


Four:



Here are the builder pieces:



In ImmutableUpdater class:



public static UpdateBuilder<T> With<T, TMember>(this T obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
ConstructorInfo immutableUpdateCtor = ImmutableUpdateConstructor(typeof(T));

return new UpdateBuilder<T>(obj, immutableUpdateCtor).With(memberSelector, newValue);
}

public static UpdateBuilder<T> With<T, TMember>(this UpdateBuilder<T> obj, Expression<Func<T, TMember>> memberSelector, TMember newValue)
{
if (!(memberSelector.Body is MemberExpression memberExpression))
{
throw new ArgumentException($"You must select a member. Affected expression '{memberSelector}'.");
}

if (!(memberExpression.Member is PropertyInfo selectedProperty))
{
throw new ArgumentException($"You must select a property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.GetSetMethod() != null)
{
throw new ArgumentException(
$"You must select a readonly property. Affected expression '{memberSelector}'.");
}

if (selectedProperty.Name.GetBackingField<T>() == null)
{
throw new ArgumentException(
$"You must select a pure readonly property (not a computed one). Affected expression '{memberSelector}'.");
}

var updates =
from property in obj.ImmutableProperties()
where property.Name == selectedProperty.Name
select
(
property, (object)newValue
);

return obj.Add(updates);
}

private static IEnumerable<PropertyInfo> ImmutableProperties<T>(this UpdateBuilder<T> obj)
{
return
typeof(T)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(propertyInfo => propertyInfo.GetSetMethod() is null);
}


and finally, the UpdateBuilder<T> class:



using System.Linq;
using System.Reflection;

using IEnumerablePropertyValue = System.Collections.Generic.IEnumerable<(System.Reflection.PropertyInfo Property, object Value)>;
using PropertyValueList = System.Collections.Generic.List<(System.Reflection.PropertyInfo Property, object Value)>;

public sealed class UpdateBuilder<T>
{
private readonly PropertyValueList _updates = new PropertyValueList();

private readonly ConstructorInfo _immutableUpdateCtor;

public UpdateBuilder(T obj, ConstructorInfo immutableUpdateCtor)
{
this.Object = obj;
this._immutableUpdateCtor = immutableUpdateCtor;
}

public T Object { get; }

public UpdateBuilder<T> Add(IEnumerablePropertyValue updates)
{
foreach (var update in updates ?? Enumerable.Empty<(PropertyInfo Property, object Value)>())
{
this._updates.Add(update);
}

return this;
}

public static implicit operator T(UpdateBuilder<T> updateBuilder)
{
if (updateBuilder == null)
{
return default(T);
}

if (updateBuilder._immutableUpdateCtor == null)
{
return updateBuilder.Object;
}

return (T)updateBuilder._immutableUpdateCtor.Invoke(new object { new ImmutableUpdate(updateBuilder._updates) });
}






share|improve this answer














share|improve this answer



share|improve this answer








edited Dec 20 at 19:58

























answered Dec 20 at 17:34









Jesse C. Slicer

11.3k2740




11.3k2740








  • 1




    Ooops, it stayed there after moving it do the helper ;-]
    – t3chb0t
    Dec 20 at 17:35






  • 1




    You must like it ;-) I see you've added a builder. I'll definitely borrow it.
    – t3chb0t
    Dec 20 at 20:21














  • 1




    Ooops, it stayed there after moving it do the helper ;-]
    – t3chb0t
    Dec 20 at 17:35






  • 1




    You must like it ;-) I see you've added a builder. I'll definitely borrow it.
    – t3chb0t
    Dec 20 at 20:21








1




1




Ooops, it stayed there after moving it do the helper ;-]
– t3chb0t
Dec 20 at 17:35




Ooops, it stayed there after moving it do the helper ;-]
– t3chb0t
Dec 20 at 17:35




1




1




You must like it ;-) I see you've added a builder. I'll definitely borrow it.
– t3chb0t
Dec 20 at 20:21




You must like it ;-) I see you've added a builder. I'll definitely borrow it.
– t3chb0t
Dec 20 at 20:21


















draft saved

draft discarded




















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.





Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


Please pay close attention to the following guidance:


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210056%2fimmutable-type-updater-using-a-special-constructor%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Сан-Квентин

Алькесар

Josef Freinademetz