1
\$\begingroup\$

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:

  1. Is this an example of functional design?
  2. If not #1, then how could I refactor into a functional design?
  3. 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.

\$\endgroup\$
2
\$\begingroup\$

Functional and OO mix

This is kind of a mix of functional and OO programming because the class is carring a state. In a pure functional way you would provide the method with all it needs this means it would require the generic <T> parameter and it would be an extension:

public static IEnumerable<DifferenceBag> CompareTo<T>(this T source, T target)
{
    // perform the T reflection etc
}

or with pre-reflection you could have another function that takes a collection of properties that you want to compare:

public static IEnumerable<DifferenceBag> CompareTo<T>(this T source, T target, IEnumerable<PropertyInfo> properties)
{
    // perform the T reflection outside
}

this means you could later jus say:

var differences = foo.CompareTo(bar);

or

var comparableProperties = typeof(T).GetComparableProperties();
var differences = foo.CompareTo(bar, comparableProperties);

or even

var differences = foo.CompareTo(bar, GetComparableProperties);

if you designed the CompareTo method in such a way that it can take a Func for the reflection part:

public static IEnumerable<DifferenceBag> CompareTo<T>(this T source, T target, Func<T, IEnumerable<PropertyInfo>> getProperties)
{
    // perform the T reflection outside
}

where

static IEnumerable<PropertyInfo> GetComparableProperties<T>() {}

Implementation

You could improve the current implementatio to be more functional by using more LINQ. The Compare method could work like this

var differences =
    from p in _properties       
    let pair = (left: p.GetValue(source), right: p.GetValue(target))
    where !pair.left.Equals(pair.right)
    select new DifferenceBag(p.Name, pair.left, pair.right, p);

if (_properties.Count == 0)
  return EmptyBag;

These lines are unnecessary because if there are not properties the resulting collection will be empty anwyway.


The extreme C# 7 solution could be implemented like this

public delegate IEnumerable<PropertyInfo> GetPropertiesCallback<T>();

public static IEnumerable<DifferenceBag> CompareTo<T>(
    this T source,
    T target,
    GetPropertiesCallback<T> getProperties)
    where T : class // required if you want to use "?? throw"
{
    var differences =
        from p in getProperties()
        let pair = (
            left: p.GetValue(source ?? throw new ArgumentNullException(nameof(source))),
            right: p.GetValue(target ?? throw new ArgumentNullException(nameof(source)))
        )
        where !pair.left.Equals(pair.right)
        select new DifferenceBag(p.Name, pair.left, pair.right, p);

    return differences;
}

Possible bug

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]);
}

If a property is decorated with both attributes you'll end up having it twice in the final list.

\$\endgroup\$
  • \$\begingroup\$ @PSGuy I've added one more example in the Implementation section. \$\endgroup\$ – t3chb0t Apr 14 '17 at 20:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.