简体   繁体   中英

How to enforce adding new public properties to the Equals method for IEquatable<T> classes

I have classes that contain other child classes, so I have implemented IEquatable<T> to perform a custom Equals method recursively down the chain. That is working fine, but I am thinking if other developers need to add new public properties to these classes, we would want to enforce that they also add them to the Equals method. I was wondering if there is an easy way to do this without reflection? Could I somehow leverage custom attributes? I'd basically like to add it to the unit test suite so that the build would fail.

If you want to force that your equality methods are built in a standard way, perhaps you could compile them at runtime using reflection;

    private static Expression Equality(Type propType, MemberExpression thisProp, MemberExpression otherProp)
    {
        var equatable = typeof(IEquatable<>).MakeGenericType(propType);
        var equal = Expression.Equal(thisProp, otherProp);

        if (!equatable.IsAssignableFrom(propType))
            return equal;

        // a == b || (a!=null && a.Equals(b))
        return Expression.OrElse(
            equal, 
            Expression.AndAlso(
                Expression.NotEqual(thisProp, Expression.Constant(null, propType)),
                Expression.Call(thisProp, equatable.GetMethod("Equals"), otherProp)
            )
        );
    }

    private static Delegate GenerateEquatable(Type type)
    {
        var thisParm = Expression.Parameter(type, "a");
        var otherParm = Expression.Parameter(type, "b");
        return Expression.Lambda(
            type.GetProperties()
                .Where(prop => prop.CanRead)
                .Select(prop => Equality(
                    prop.PropertyType,
                    Expression.MakeMemberAccess(thisParm, prop),
                    Expression.MakeMemberAccess(otherParm, prop)))
                .Aggregate((a, b) => Expression.AndAlso(a, b)),
            thisParm, otherParm).Compile();
    }

    public static Func<T, T, bool> GenerateEquatable<T>() where T:IEquatable<T> =>
        (Func<T, T, bool>)GenerateEquatable(typeof(T));

    public class Foo : IEquatable<Foo>{
        private static Func<Foo, Foo, bool> _equals = GenerateEquatable<Foo>();
        public bool Equals([AllowNull] Foo other) => other != null && _equals(this, other);
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM