diff --git a/Src/FluentAssertions/Common/ExpressionExtensions.cs b/Src/FluentAssertions/Common/ExpressionExtensions.cs index c99b0f552a..82a22d8e46 100644 --- a/Src/FluentAssertions/Common/ExpressionExtensions.cs +++ b/Src/FluentAssertions/Common/ExpressionExtensions.cs @@ -158,10 +158,5 @@ internal static class ExpressionExtensions return new MemberPath(declaringType, segmentPath.Replace(".[", "[")); } - - internal static string GetMethodName(Expression action) - { - return ((MethodCallExpression)action.Body).Method.Name; - } } } diff --git a/Src/FluentAssertions/Equivalency/GenericDictionaryEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/GenericDictionaryEquivalencyStep.cs index 225a4a8065..9744027415 100644 --- a/Src/FluentAssertions/Equivalency/GenericDictionaryEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/GenericDictionaryEquivalencyStep.cs @@ -1,9 +1,8 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Reflection; -using FluentAssertions.Common; using FluentAssertions.Execution; namespace FluentAssertions.Equivalency @@ -14,6 +13,12 @@ namespace FluentAssertions.Equivalency /// public class GenericDictionaryEquivalencyStep : IEquivalencyStep { + private static readonly MethodInfo AssertSameLengthMethod = new Func, IDictionary, bool> + (AssertSameLength).GetMethodInfo().GetGenericMethodDefinition(); + + private static readonly MethodInfo AssertDictionaryEquivalenceMethod = new Action, IDictionary> + (AssertDictionaryEquivalence).GetMethodInfo().GetGenericMethodDefinition(); + public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAssertionOptions config) { Type expectationType = config.GetExpectationType(context); @@ -135,22 +140,17 @@ private static Type GetDictionaryKeyType(Type expectedType) private static bool AssertSameLength(object subject, Type expectationType, object expectation) { - string methodName = - ExpressionExtensions.GetMethodName(() => AssertSameLength(null, null)); + if(subject is ICollection subjectCollection + && expectation is ICollection expectationCollection + && subjectCollection.Count == expectationCollection.Count) + return true; Type subjectType = subject.GetType(); Type[] subjectTypeArguments = GetDictionaryTypeArguments(subjectType); Type[] expectationTypeArguments = GetDictionaryTypeArguments(expectationType); Type[] typeArguments = subjectTypeArguments.Concat(expectationTypeArguments).ToArray(); - MethodCallExpression assertSameLength = Expression.Call( - typeof(GenericDictionaryEquivalencyStep), - methodName, - typeArguments, - Expression.Constant(subject, GetIDictionaryInterface(subjectType)), - Expression.Constant(expectation, GetIDictionaryInterface(expectationType))); - - return (bool)Expression.Lambda(assertSameLength).Compile().DynamicInvoke(); + return (bool)AssertSameLengthMethod.MakeGenericMethod(typeArguments).Invoke(null, new[] { subject, expectation }); } private static Type[] GetDictionaryTypeArguments(Type type) @@ -233,27 +233,12 @@ private static Type GetIDictionaryInterface(Type expectedType) IEquivalencyValidator parent, IEquivalencyAssertionOptions config) { Type expectationType = config.GetExpectationType(context); - - string methodName = - ExpressionExtensions.GetMethodName( - () => AssertDictionaryEquivalence(null, null, null, null, null)); - Type subjectType = context.Subject.GetType(); Type[] subjectTypeArguments = GetDictionaryTypeArguments(subjectType); Type[] expectationTypeArguments = GetDictionaryTypeArguments(expectationType); Type[] typeArguments = subjectTypeArguments.Concat(expectationTypeArguments).ToArray(); - MethodCallExpression assertDictionaryEquivalence = Expression.Call( - typeof(GenericDictionaryEquivalencyStep), - methodName, - typeArguments, - Expression.Constant(context), - Expression.Constant(parent), - Expression.Constant(config), - Expression.Constant(context.Subject, GetIDictionaryInterface(subjectType)), - Expression.Constant(context.Expectation, GetIDictionaryInterface(expectationType))); - - Expression.Lambda(assertDictionaryEquivalence).Compile().DynamicInvoke(); + AssertDictionaryEquivalenceMethod.MakeGenericMethod(typeArguments).Invoke(null, new[] { context, parent, config, context.Subject, context.Expectation }); } private static void AssertDictionaryEquivalence( diff --git a/Src/FluentAssertions/Equivalency/GenericEnumerableEquivalencyStep.cs b/Src/FluentAssertions/Equivalency/GenericEnumerableEquivalencyStep.cs index d80929327e..adf5cf5482 100644 --- a/Src/FluentAssertions/Equivalency/GenericEnumerableEquivalencyStep.cs +++ b/Src/FluentAssertions/Equivalency/GenericEnumerableEquivalencyStep.cs @@ -2,7 +2,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using System.Reflection; using FluentAssertions.Common; using FluentAssertions.Execution; @@ -11,6 +10,9 @@ namespace FluentAssertions.Equivalency { public class GenericEnumerableEquivalencyStep : IEquivalencyStep { + private static readonly MethodInfo HandleMethod = new Action> + (HandleImpl).GetMethodInfo().GetGenericMethodDefinition(); + /// /// Gets a value indicating whether this step can handle the verificationScope subject and/or expectation. /// @@ -36,14 +38,13 @@ public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAsserti { Type expectedType = config.GetExpectationType(context); - var interfaceTypes = GetIEnumerableInterfaces(expectedType) - .Select(type => "IEnumerable<" + type.GetGenericArguments().Single() + ">") - .ToList(); + var interfaceTypes = GetIEnumerableInterfaces(expectedType); AssertionScope.Current - .ForCondition(interfaceTypes.Count == 1) - .FailWith("{context:Expectation} implements {0}, so cannot determine which one " + - "to use for asserting the equivalency of the collection. ", interfaceTypes); + .ForCondition(interfaceTypes.Length == 1) + .FailWith(() => new FailReason("{context:Expectation} implements {0}, so cannot determine which one " + + "to use for asserting the equivalency of the collection. ", + interfaceTypes.Select(type => "IEnumerable<" + type.GetGenericArguments().Single() + ">"))); if (AssertSubjectIsCollection(context.Expectation, context.Subject)) { @@ -55,20 +56,11 @@ public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAsserti Type typeOfEnumeration = GetTypeOfEnumeration(expectedType); - MethodCallExpression expectationAsArray = ToArray(context.Expectation, typeOfEnumeration); - ConstantExpression subjectAsArray = - Expression.Constant(EnumerableEquivalencyStep.ToArray(context.Subject)); - - MethodCallExpression executeExpression = Expression.Call( - Expression.Constant(validator), - ExpressionExtensions.GetMethodName(() => validator.Execute(null, null)), - new[] {typeOfEnumeration}, - subjectAsArray, - expectationAsArray); + var subjectAsArray = EnumerableEquivalencyStep.ToArray(context.Subject); try { - Expression.Lambda(executeExpression).Compile().DynamicInvoke(); + HandleMethod.MakeGenericMethod(typeOfEnumeration).Invoke(null, new[] { validator, subjectAsArray, context.Expectation }); } catch (TargetInvocationException e) { @@ -79,6 +71,9 @@ public bool CanHandle(IEquivalencyValidationContext context, IEquivalencyAsserti return true; } + private static void HandleImpl(EnumerableEquivalencyValidator validator, object[] subject, IEnumerable expectation) + => validator.Execute(subject, expectation?.ToArray()); + private static bool AssertSubjectIsCollection(object expectation, object subject) { bool conditionMet = AssertionScope.Current @@ -120,14 +115,5 @@ private static Type GetTypeOfEnumeration(Type enumerableType) return interfaceType.GetGenericArguments().Single(); } - - private static MethodCallExpression ToArray(object value, Type typeOfEnumeration) - { - return Expression.Call( - typeof(Enumerable), - "ToArray", - new[] {typeOfEnumeration}, - Expression.Constant(value, typeof(IEnumerable<>).MakeGenericType(typeOfEnumeration))); - } } } diff --git a/Src/FluentAssertions/Equivalency/ObjectReference.cs b/Src/FluentAssertions/Equivalency/ObjectReference.cs index 68c6382301..d23ec6c754 100644 --- a/Src/FluentAssertions/Equivalency/ObjectReference.cs +++ b/Src/FluentAssertions/Equivalency/ObjectReference.cs @@ -10,18 +10,14 @@ namespace FluentAssertions.Equivalency internal class ObjectReference { private readonly object @object; - private readonly string[] path; + private readonly string path; private readonly bool? isComplexType; + private string[] pathElements; public ObjectReference(object @object, string path, bool? isComplexType = null) { this.@object = @object; - - this.path = path - .ToLower() - .Replace("][", "].[") - .Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); - + this.path = path; this.isComplexType = isComplexType; } @@ -42,9 +38,14 @@ public override bool Equals(object obj) return ReferenceEquals(@object, other.@object) && IsParentOf(other); } + private string[] GetPathElements() => pathElements + ?? (pathElements = path.ToLowerInvariant().Replace("][", "].[").Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries)); + private bool IsParentOf(ObjectReference other) { - return (other.path.Length > path.Length) && other.path.Take(path.Length).SequenceEqual(path); + string[] path = GetPathElements(); + string[] otherPath = other.GetPathElements(); + return (otherPath.Length > path.Length) && otherPath.Take(path.Length).SequenceEqual(path); } /// @@ -56,15 +57,12 @@ private bool IsParentOf(ObjectReference other) /// 2 public override int GetHashCode() { - unchecked - { - return (@object.GetHashCode() * 397) ^ path.GetHashCode(); - } + return System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(@object); } public override string ToString() { - return $"{{\"{string.Join(".", path)}\", {@object}}}"; + return $"{{\"{path}\", {@object}}}"; } public bool IsComplexType => isComplexType ?? (!(@object is null) && !@object.GetType().OverridesEquals());