I just started learning about functional programming, and thought I would have a look at writing a simple object comparer as the basis for my implementation. Below is my result.
My assumption (in writing) is that functional code is intended to not modify external state, and not have side effects. If I functional style correctly, pure functions are ones who operate only on their operands.
The link to all the source code is on GitHub in a public repo, which can be found here. The most relevant bits are below:
public class DifferenceBag
{
public string PropertyName { get; }
public PropertyInfo PropertyInfo { get; }
public object LeftHandValue { get; }
public object RightHandValue { get; }
}
The main object constructor is here:
private const BindingFlags PublicPropertyBindingFlags = BindingFlags.Public | BindingFlags.Instance;
private readonly ImmutableList<PropertyInfo> _properties;
public ObjectComparer()
{
var propertySet = typeof(T).GetProperties(PublicPropertyBindingFlags);
var builder = ImmutableList.CreateBuilder<PropertyInfo>();
for (var i = 0; i < propertySet.Length; i++)
{
var immutable = propertySet[i].GetCustomAttribute<ImmutableAttribute>();
if (immutable != null && immutable.IsTargetMutable)
{
builder.Add(propertySet[i]);
}
var ignore = propertySet[i].GetCustomAttribute<IgnoreCompareAttribute>();
if (ignore != null && !ignore.SkipProperty)
{
builder.Add(propertySet[i]);
}
}
_properties = builder.ToImmutable();
}
And the primary method:
public static readonly IEnumerable<T> EmptyBag = new T[0];
public IEnumerable<DifferenceBag> Compare(T source, T target)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (target == null)
throw new ArgumentNullException(nameof(target));
// If either target is null or our property bag list is empty,
// then just bail.
if (_properties.Count == 0)
return EmptyBag;
var list = new LinkedList<DifferenceBag>();
foreach (var property in _properties)
{
var src = property.GetValue(source);
var tgt = property.GetValue(target);
// By calling object.Equals(object, object), we can avoid null-reference exceptions.
if (!object.Equals(src, tgt))
list.AddLast(new DifferenceBag(property.Name, src, tgt, property));
}
return list;
}
}
So, ObjectComparer<T>
acquires the property list during construction. This is state, but it's local to the comparer instance. That state is then used during the IEnumerable<DifferenceBag> Compare(T, T)
routine.
So, my questions are:
- Is this an example of functional design?
- If not #1, then how could I refactor into a functional design?
- Is there any benefit #2?
My interest in this topic is not constrained to the context of this object, but the context of patterns in general.