patterncsharpMinor
Generic immutable object builder
Viewed 0 times
genericimmutableobjectbuilder
Problem
I've made an object builder which can create all sorts of classes, it can also create immutable objects.
There are 2 requirements that must be met in order for this pattern to work with fully immutable objects.
Here is the actual builder class :
```
public class ImmutableObjectBuilder
where T : class
{
private class PropertyWrapper
{
public PropertyInfo Property { get; }
public object Value { get; set; }
internal PropertyWrapper(PropertyInfo property, object value)
{
Property = property;
Value = value;
}
}
private readonly IDictionary propertiesInfo = new Dictionary();
public ImmutableObjectBuilder()
{
PropertyInfo[] properties = typeof(T).GetProperties();
foreach (PropertyInfo p in properties)
{
propertiesInfo.Add(p.Name, new PropertyWrapper(p, null));
}
}
public ImmutableObjectBuilder WithValue(string propertyName, TValue value)
{
if (!propertiesInfo.ContainsKey(propertyName))
{
throw new KeyNotFoundException("The type of TValue is different than the type of T");
}
propertiesInfo[propertyName].Value = value;
return this;
}
public TObject Build(Func instance)
where TObject : T
{
TObject localInstance = instance();
PropertyInfo[] instanceProperties = localInstance.GetType().GetProperties();
PropertyInfo[] matchingProperties =
(from tProperty in instanceProperties
from localProperty in propertiesInfo
where tProperty.Name == localProperty.Key &&
tProperty.PropertyType == localProperty.Value.Property.Pr
There are 2 requirements that must be met in order for this pattern to work with fully immutable objects.
- The properties that you want to be modified must have a backing readonly field.
- The backing field must follow some convention in my case it must start with small letter and optionally it can start with _ too, of course this can be extended to match your preferences.
Here is the actual builder class :
```
public class ImmutableObjectBuilder
where T : class
{
private class PropertyWrapper
{
public PropertyInfo Property { get; }
public object Value { get; set; }
internal PropertyWrapper(PropertyInfo property, object value)
{
Property = property;
Value = value;
}
}
private readonly IDictionary propertiesInfo = new Dictionary();
public ImmutableObjectBuilder()
{
PropertyInfo[] properties = typeof(T).GetProperties();
foreach (PropertyInfo p in properties)
{
propertiesInfo.Add(p.Name, new PropertyWrapper(p, null));
}
}
public ImmutableObjectBuilder WithValue(string propertyName, TValue value)
{
if (!propertiesInfo.ContainsKey(propertyName))
{
throw new KeyNotFoundException("The type of TValue is different than the type of T");
}
propertiesInfo[propertyName].Value = value;
return this;
}
public TObject Build(Func instance)
where TObject : T
{
TObject localInstance = instance();
PropertyInfo[] instanceProperties = localInstance.GetType().GetProperties();
PropertyInfo[] matchingProperties =
(from tProperty in instanceProperties
from localProperty in propertiesInfo
where tProperty.Name == localProperty.Key &&
tProperty.PropertyType == localProperty.Value.Property.Pr
Solution
I would prefer the following syntax for its compile-time type-checking:
Enabled by the following version of
There is a requirement that the expression is of the form
Employee John = humanBuilder
.WithValue(t => t.Name, "John")
.WithValue(t => t.Age, 32)
.Build();Enabled by the following version of
WithValue:public ImmutableObjectBuilder WithValue(
Expression> property, TValue value)
{
var body = property.Body as MemberExpression;
if (body == null)
{
throw new InvalidOperationException("Improperly formatted expression");
}
var propertyName = body.Member.Name;
propertiesInfo[propertyName].Value = value;
return this;
}There is a requirement that the expression is of the form
t => t.SimplePropertyAccess which cannot be enforced at compile-time, but at least you do get intellisense and compile-time type-checking.Code Snippets
Employee John = humanBuilder
.WithValue(t => t.Name, "John")
.WithValue(t => t.Age, 32)
.Build<Employee>();public ImmutableObjectBuilder<T> WithValue<TValue>(
Expression<Func<T, TValue>> property, TValue value)
{
var body = property.Body as MemberExpression;
if (body == null)
{
throw new InvalidOperationException("Improperly formatted expression");
}
var propertyName = body.Member.Name;
propertiesInfo[propertyName].Value = value;
return this;
}Context
StackExchange Code Review Q#151078, answer score: 8
Revisions (0)
No revisions yet.