diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..969cbdc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,237 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space +# (Please don't specify an indent_size here; that has too many unintended consequences.) + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 +insert_final_newline = false +charset = utf-8-bom +end_of_line = crlf + +# XML project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +[*.{yaml,yml}] +indent_size = 2 + +# Powershell files +[*.ps1] +indent_size = 2 + +# sql files +[*.sql] +indent_size = 2 + +# Shell script files +[*.sh] +end_of_line = lf +indent_size = 2 + +############################## +# Dotnet code style settings # +############################## +[*.{cs,vb}] +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true + +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:refactoring +dotnet_style_qualification_for_property = false:refactoring +dotnet_style_qualification_for_method = false:refactoring +dotnet_style_qualification_for_event = false:refactoring + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion + +# Non-private static fields are PascalCase +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.symbols = non_private_static_fields +dotnet_naming_rule.non_private_static_fields_should_be_pascal_case.style = non_private_static_field_style + +dotnet_naming_symbols.non_private_static_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_static_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected +dotnet_naming_symbols.non_private_static_fields.required_modifiers = static + +dotnet_naming_style.non_private_static_field_style.capitalization = pascal_case + +# Non-private readonly fields are PascalCase +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.symbols = non_private_readonly_fields +dotnet_naming_rule.non_private_readonly_fields_should_be_pascal_case.style = non_private_readonly_field_style + +dotnet_naming_symbols.non_private_readonly_fields.applicable_kinds = field +dotnet_naming_symbols.non_private_readonly_fields.applicable_accessibilities = public, protected, internal, protected_internal, private_protected +dotnet_naming_symbols.non_private_readonly_fields.required_modifiers = readonly + +dotnet_naming_style.non_private_readonly_field_style.capitalization = pascal_case + +# Constants are PascalCase +dotnet_naming_rule.constants_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = constant_style + +dotnet_naming_symbols.constants.applicable_kinds = field, local +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_style.constant_style.capitalization = pascal_case + +# Internal or Private static fields are camelCase and start with s_ +dotnet_naming_rule.static_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.static_fields_should_be_camel_case.symbols = static_fields +dotnet_naming_rule.static_fields_should_be_camel_case.style = static_field_style + +dotnet_naming_symbols.static_fields.applicable_kinds = field +dotnet_naming_symbols.static_fields.applicable_accessibilities = internal, private +dotnet_naming_symbols.static_fields.required_modifiers = static + +dotnet_naming_style.static_field_style.capitalization = camel_case +dotnet_naming_style.static_field_style.required_prefix = s_ + +# Instance fields are camelCase and start with _ +dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields +dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style + +dotnet_naming_symbols.instance_fields.applicable_kinds = field + +dotnet_naming_style.instance_field_style.capitalization = camel_case +dotnet_naming_style.instance_field_style.required_prefix = _ + +# Locals and parameters are camelCase +dotnet_naming_rule.locals_should_be_camel_case.severity = suggestion +dotnet_naming_rule.locals_should_be_camel_case.symbols = locals_and_parameters +dotnet_naming_rule.locals_should_be_camel_case.style = camel_case_style + +dotnet_naming_symbols.locals_and_parameters.applicable_kinds = parameter, local + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +# Local functions are PascalCase +dotnet_naming_rule.local_functions_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.local_functions_should_be_pascal_case.symbols = local_functions +dotnet_naming_rule.local_functions_should_be_pascal_case.style = local_function_style + +dotnet_naming_symbols.local_functions.applicable_kinds = local_function + +dotnet_naming_style.local_function_style.capitalization = pascal_case + +# By default, name items with PascalCase +dotnet_naming_rule.members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.members_should_be_pascal_case.symbols = all_members +dotnet_naming_rule.members_should_be_pascal_case.style = pascal_case_style + +dotnet_naming_symbols.all_members.applicable_kinds = * + +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +############################## +# CSharp code style settings # +############################## +[*.cs] +# IDE0160: Convert to file-scoped namespace +# csharp_style_namespace_declarations = file_scoped:warning +csharp_style_namespace_declarations = block_scoped:warning + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_switch_labels = true +csharp_indent_labels = flush_left + +# Prefer "var" everywhere +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# Prefer method-like constructs to have a block body +csharp_style_expression_bodied_methods = false:none +csharp_style_expression_bodied_constructors = false:none +csharp_style_expression_bodied_operators = false:none + +# Prefer property-like constructs to have an expression-body +csharp_style_expression_bodied_properties = true:none +csharp_style_expression_bodied_indexers = true:none +csharp_style_expression_bodied_accessors = true:none + +# Suggest more modern language features when available +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = do_not_ignore +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Blocks are allowed +csharp_prefer_braces = true:silent +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = none + +## Not needed when nullable references is enabled +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = none + +# CA1716: Identifiers should not match keywords +dotnet_diagnostic.CA1716.severity = none + +# Workaround for https://github.com/dotnet/roslyn-analyzers/issues/5628 +[Program.cs] +dotnet_diagnostic.ca1812.severity = none \ No newline at end of file diff --git a/LambdaCompare.sln b/LambdaCompare.sln index 06684a0..4e01cc7 100644 --- a/LambdaCompare.sln +++ b/LambdaCompare.sln @@ -1,14 +1,15 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26403.7 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33414.496 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neleus.LambdaCompare", "Neleus.LambdaCompare\Neleus.LambdaCompare.csproj", "{28179CB7-80D1-4F8C-B171-97DABAB7C874}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neleus.LambdaCompare", "Neleus.LambdaCompare\Neleus.LambdaCompare.csproj", "{28179CB7-80D1-4F8C-B171-97DABAB7C874}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neleus.LambdaCompare.Tests", "Neleus.LambdaCompare.Tests\Neleus.LambdaCompare.Tests.csproj", "{D5556DE2-1758-4EF1-9835-3807E9DED85F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neleus.LambdaCompare.Tests", "Neleus.LambdaCompare.Tests\Neleus.LambdaCompare.Tests.csproj", "{D5556DE2-1758-4EF1-9835-3807E9DED85F}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{79CD7955-3850-4C9A-8D11-1322ED8FAF4C}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig LICENSE = LICENSE README.md = README.md EndProjectSection diff --git a/Neleus.LambdaCompare.Tests/Neleus.LambdaCompare.Tests.csproj b/Neleus.LambdaCompare.Tests/Neleus.LambdaCompare.Tests.csproj index 0a5b7a0..7cf70b7 100644 --- a/Neleus.LambdaCompare.Tests/Neleus.LambdaCompare.Tests.csproj +++ b/Neleus.LambdaCompare.Tests/Neleus.LambdaCompare.Tests.csproj @@ -1,24 +1,24 @@  - netcoreapp1.0;netcoreapp2.0;net461 + net4.6.2;net6.0;net7.0 Neleus.LambdaCompare.Tests Neleus.LambdaCompare.Tests false - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - - - - diff --git a/Neleus.LambdaCompare.Tests/Tests.cs b/Neleus.LambdaCompare.Tests/Tests.cs index dc85558..33c315e 100644 --- a/Neleus.LambdaCompare.Tests/Tests.cs +++ b/Neleus.LambdaCompare.Tests/Tests.cs @@ -1,63 +1,149 @@ -using System; +using System; using System.Globalization; using System.Linq.Expressions; -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Xunit; +using FluentAssertions; +using System.Collections.Generic; +using System.Reflection; namespace Neleus.LambdaCompare.Tests { - [TestClass] public class Tests { - [TestMethod] + [Fact] public void BasicConst() { var const1 = 25; - var f1 = (Expression>) ((first, second) => + var f1 = (Expression>)((first, second) => $"{(first + const1).ToString(CultureInfo.InvariantCulture)}{first + second}{"some const value".ToUpper()}{const1}"); var const2 = "some const value"; var const3 = "{0}{1}{2}{3}"; - var f2 = (Expression>) ((i, s) => + var f2 = (Expression>)((i, s) => string.Format(const3, (i + 25).ToString(CultureInfo.InvariantCulture), i + s, const2.ToUpper(), 25)); - Assert.IsTrue(Lambda.Eq(f1, f2)); + Lambda.Eq(f1, f2).Should().BeTrue(); } - [TestMethod] + [Fact] public void PropAndMethodCall() { - var f1 = (Expression>) (arg1 => Uri.IsWellFormedUriString(arg1.ToString(), UriKind.Absolute)); - var f2 = (Expression>) (u => Uri.IsWellFormedUriString(u.ToString(), UriKind.Absolute)); + var f1 = (Expression>)(arg1 => Uri.IsWellFormedUriString(arg1.ToString(), UriKind.Absolute)); + var f2 = (Expression>)(u => Uri.IsWellFormedUriString(u.ToString(), UriKind.Absolute)); - Assert.IsTrue(Lambda.Eq(f1, f2)); + Lambda.Eq(f1, f2).Should().BeTrue(); } - [TestMethod] + [Fact] public void MemberInitWithConditional() { var port = 443; - var f1 = (Expression>) (x => new UriBuilder(x) + var f1 = (Expression>)(x => new UriBuilder(x) { Port = port, Host = string.IsNullOrEmpty(x.Host) ? "abc" : "def" }); var isSecure = true; - var f2 = (Expression>) (u => new UriBuilder(u) + var f2 = (Expression>)(u => new UriBuilder(u) { Host = string.IsNullOrEmpty(u.Host) ? "abc" : "def", Port = isSecure ? 443 : 80 }); - Assert.IsTrue(Lambda.Eq(f1, f2)); + Lambda.Eq(f1, f2).Should().BeTrue(); } - [TestMethod, Ignore] - public void AnonymousType() + [Fact] + public void AnonymousType_Not_Supported() { - var f1 = (Expression>) (x => new { Port = 443, x.Host, Addr = x.AbsolutePath }); - var f2 = (Expression>) (u => new { u.Host, Port = 443, Addr = u.AbsolutePath }); - Assert.Inconclusive("Anonymous Types are not supported"); + var f1 = (Expression>)(x => new { Port = 443, x.Host, Addr = x.AbsolutePath }); + var f2 = (Expression>)(u => new { u.Host, Port = 443, Addr = u.AbsolutePath }); + + var sut = (Func)(() => Lambda.Eq(f1, f2)); + + sut.Should() + .Throw() + .WithMessage("Comparison of Anonymous Types is not supported"); + } + + [Fact] + public void Nulls_are_compared_correctly() + { + var f1 = (Expression>)(_ => ""); + var f2 = (Expression>)(null); + var f3 = (Expression>)(null); + + Lambda.Eq(f1, f2).Should().BeFalse(); + Lambda.Eq(f2, f3).Should().BeTrue(); + } + + [Fact] + public void Calculations_are_compared_correctly() + { + var f1 = (Expression>)(_ => 1 + 2); + var f2 = (Expression>)(_ => 3); + + Lambda.Eq(f1, f2).Should().BeTrue(); + } + + [Fact] + public void Member_init_expressions_are_compared_correctly() + { + var addMethod = typeof(List).GetTypeInfo().GetDeclaredMethod("Add"); + + var bindingMessages = Expression.ListBind( + typeof(Node).GetProperty("Messages"), + Expression.ElementInit(addMethod, Expression.Constant("Constant1")) + ); + + var bindingDescriptions = Expression.ListBind( + typeof(Node).GetProperty("Descriptions"), + Expression.ElementInit(addMethod, Expression.Constant("Constant2")) + ); + + Expression e1 = Expression.MemberInit( + Expression.New(typeof(Node)), + new List { bindingMessages } + ); + + Expression e2 = Expression.MemberInit( + Expression.New(typeof(Node)), + new List { bindingMessages, bindingDescriptions } + ); + + Lambda.ExpressionsEqual(e1, e2).Should().BeFalse(); + Lambda.ExpressionsEqual(e1, e1).Should().BeTrue(); + } + + [Fact] + public void Default_expressions_are_compared_correctly() + { + Expression e1 = Expression.Default(typeof(int)); + Expression e2 = Expression.Default(typeof(int)); + Expression e3 = Expression.Default(typeof(string)); + + Lambda.ExpressionsEqual(e1, e2).Should().BeTrue(); + Lambda.ExpressionsEqual(e1, e3).Should().BeFalse(); + Lambda.ExpressionsEqual(e1, e1).Should().BeTrue(); + } + + [Fact] + public void Array_constant_expressions_are_compared_correctly() + { + var e1 = Expression.Constant(new[] { 1, 2, 3 }); + var e2 = Expression.Constant(new[] { 1, 2, 3 }); + var e3 = Expression.Constant(new[] { 1, 2, 4 }); + + Lambda.ExpressionsEqual(e1, e2).Should().BeTrue(); + Lambda.ExpressionsEqual(e1, e3).Should().BeFalse(); + } + + private class Node + { + public List Messages { set; get; } + + public List Descriptions { set; get; } } } -} +} \ No newline at end of file diff --git a/Neleus.LambdaCompare/Comparer.cs b/Neleus.LambdaCompare/Comparer.cs index 7e06a4f..afe33b2 100644 --- a/Neleus.LambdaCompare/Comparer.cs +++ b/Neleus.LambdaCompare/Comparer.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -16,10 +16,14 @@ public static bool ExpressionsEqual(Expression x, Expression y, LambdaExpression if (x == null || y == null) return false; var valueX = TryCalculateConstant(x); - var valueY = TryCalculateConstant(y); - - if (valueX.IsDefined && valueY.IsDefined) - return ValuesEqual(valueX.Value, valueY.Value); + if (valueX.IsDefined) + { + var valueY = TryCalculateConstant(y); + if (valueY.IsDefined) + { + return ValuesEqual(valueX.Value, valueY.Value); + } + } if (x.NodeType != y.NodeType || x.Type != y.Type) @@ -29,87 +33,78 @@ public static bool ExpressionsEqual(Expression x, Expression y, LambdaExpression return false; } - if (x is LambdaExpression) + if (x is LambdaExpression lx) { - var lx = (LambdaExpression)x; var ly = (LambdaExpression)y; var paramsX = lx.Parameters; var paramsY = ly.Parameters; return CollectionsEqual(paramsX, paramsY, lx, ly) && ExpressionsEqual(lx.Body, ly.Body, lx, ly); } - if (x is MemberExpression) + else if (x is MemberExpression mex) { - var mex = (MemberExpression)x; var mey = (MemberExpression)y; return Equals(mex.Member, mey.Member) && ExpressionsEqual(mex.Expression, mey.Expression, rootX, rootY); } - if (x is BinaryExpression) + else if (x is BinaryExpression bx) { - var bx = (BinaryExpression)x; var by = (BinaryExpression)y; return bx.Method == by.Method && ExpressionsEqual(bx.Left, by.Left, rootX, rootY) && ExpressionsEqual(bx.Right, by.Right, rootX, rootY); } - if (x is UnaryExpression) + else if (x is UnaryExpression ux) { - var ux = (UnaryExpression)x; var uy = (UnaryExpression)y; return ux.Method == uy.Method && ExpressionsEqual(ux.Operand, uy.Operand, rootX, rootY); } - if (x is ParameterExpression) + else if (x is ParameterExpression px) { - var px = (ParameterExpression)x; var py = (ParameterExpression)y; return rootX.Parameters.IndexOf(px) == rootY.Parameters.IndexOf(py); } - if (x is MethodCallExpression) + else if (x is MethodCallExpression mcx) { - var cx = (MethodCallExpression)x; - var cy = (MethodCallExpression)y; - return cx.Method == cy.Method - && ExpressionsEqual(cx.Object, cy.Object, rootX, rootY) - && CollectionsEqual(cx.Arguments, cy.Arguments, rootX, rootY); + var mcy = (MethodCallExpression)y; + return mcx.Method == mcy.Method + && ExpressionsEqual(mcx.Object, mcy.Object, rootX, rootY) + && CollectionsEqual(mcx.Arguments, mcy.Arguments, rootX, rootY); } - if (x is MemberInitExpression) + else if (x is MemberInitExpression mix) { - var mix = (MemberInitExpression)x; var miy = (MemberInitExpression)y; return ExpressionsEqual(mix.NewExpression, miy.NewExpression, rootX, rootY) && MemberInitsEqual(mix.Bindings, miy.Bindings, rootX, rootY); } - if (x is NewArrayExpression) + else if (x is NewArrayExpression nax) { - var nx = (NewArrayExpression)x; - var ny = (NewArrayExpression)y; - return CollectionsEqual(nx.Expressions, ny.Expressions, rootX, rootY); + var nay = (NewArrayExpression)y; + return CollectionsEqual(nax.Expressions, nay.Expressions, rootX, rootY); } - if (x is NewExpression) + else if (x is NewExpression nx) { - var nx = (NewExpression)x; var ny = (NewExpression)y; return Equals(nx.Constructor, ny.Constructor) && CollectionsEqual(nx.Arguments, ny.Arguments, rootX, rootY) - && (nx.Members == null && ny.Members == null - || nx.Members != null && ny.Members != null && CollectionsEqual(nx.Members, ny.Members)); + && ((nx.Members == null && ny.Members == null) + || (nx.Members != null && ny.Members != null && CollectionsEqual(nx.Members, ny.Members))); } - if (x is ConditionalExpression) + else if (x is ConditionalExpression cx) { - var cx = (ConditionalExpression)x; var cy = (ConditionalExpression)y; return ExpressionsEqual(cx.Test, cy.Test, rootX, rootY) && ExpressionsEqual(cx.IfFalse, cy.IfFalse, rootX, rootY) && ExpressionsEqual(cx.IfTrue, cy.IfTrue, rootX, rootY); } + else if (x is DefaultExpression) + { + return true; + } throw new NotImplementedException(x.ToString()); } private static bool IsAnonymousType(Type type) { - var hasCompilerGeneratedAttribute = type.GetTypeInfo().GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any(); - var nameContainsAnonymousType = type.FullName.Contains("AnonymousType"); - var isAnonymousType = hasCompilerGeneratedAttribute && nameContainsAnonymousType; - - return isAnonymousType; + return type.FullName.Contains("AnonymousType") && + type.GetTypeInfo().GetCustomAttributes(typeof(CompilerGeneratedAttribute), false).Any(); } private static bool MemberInitsEqual(ICollection bx, ICollection by, LambdaExpression rootX, LambdaExpression rootY) @@ -134,47 +129,48 @@ private static bool ValuesEqual(object x, object y) { if (ReferenceEquals(x, y)) return true; - if (x is ICollection && y is ICollection) - return CollectionsEqual((ICollection)x, (ICollection)y); + if (x is ICollection collectionX && y is ICollection collectionY) + return CollectionsEqual(collectionX, collectionY); return Equals(x, y); } private static ConstantValue TryCalculateConstant(Expression e) { - if (e is ConstantExpression) - return new ConstantValue(true, ((ConstantExpression)e).Value); - if (e is MemberExpression) + if (e is ConstantExpression constantExpression) + { + return new ConstantValue(true, constantExpression.Value); + } + else if (e is MemberExpression memberExpression) { - var me = (MemberExpression)e; - var parentValue = TryCalculateConstant(me.Expression); + var parentValue = TryCalculateConstant(memberExpression.Expression); if (parentValue.IsDefined) { var result = - me.Member is FieldInfo - ? ((FieldInfo)me.Member).GetValue(parentValue.Value) - : ((PropertyInfo)me.Member).GetValue(parentValue.Value); + memberExpression.Member is FieldInfo info + ? info.GetValue(parentValue.Value) + : ((PropertyInfo)memberExpression.Member).GetValue(parentValue.Value); return new ConstantValue(true, result); } } - if (e is NewArrayExpression) + else if (e is NewArrayExpression newArrayExpression) { - var ae = ((NewArrayExpression)e); - var result = ae.Expressions.Select(TryCalculateConstant); + var result = newArrayExpression.Expressions.Select(TryCalculateConstant); if (result.All(i => i.IsDefined)) - return new ConstantValue(true, result.Select(i => i.Value).ToArray()); + return new ConstantValue(true, result.Select(i => i.Value).ToArray()); } - if (e is ConditionalExpression) + else if (e is ConditionalExpression conditionalExpression) { - var ce = (ConditionalExpression)e; - var evaluatedTest = TryCalculateConstant(ce.Test); + var evaluatedTest = TryCalculateConstant(conditionalExpression.Test); if (evaluatedTest.IsDefined) { - return TryCalculateConstant(Equals(evaluatedTest.Value, true) ? ce.IfTrue : ce.IfFalse); + return TryCalculateConstant(Equals(evaluatedTest.Value, true) + ? conditionalExpression.IfTrue + : conditionalExpression.IfFalse); } } - return default(ConstantValue); + return default; } private static bool CollectionsEqual(IEnumerable x, IEnumerable y, LambdaExpression rootX, LambdaExpression rootY) @@ -195,7 +191,7 @@ private static bool CollectionsEqual(ICollection x, ICollection y) .All(o => Equals(o.X, o.Y)); } - private struct ConstantValue + private readonly struct ConstantValue { public ConstantValue(bool isDefined, object value) : this() diff --git a/Neleus.LambdaCompare/Lambda.cs b/Neleus.LambdaCompare/Lambda.cs index 4410013..aef7101 100644 --- a/Neleus.LambdaCompare/Lambda.cs +++ b/Neleus.LambdaCompare/Lambda.cs @@ -208,4 +208,4 @@ public static bool ExpressionsEqual(Expression x, Expression y) return Comparer.ExpressionsEqual(x, y, null, null); } } -} +} \ No newline at end of file diff --git a/Neleus.LambdaCompare/Neleus.LambdaCompare.csproj b/Neleus.LambdaCompare/Neleus.LambdaCompare.csproj index 26316ce..99e36aa 100644 --- a/Neleus.LambdaCompare/Neleus.LambdaCompare.csproj +++ b/Neleus.LambdaCompare/Neleus.LambdaCompare.csproj @@ -1,7 +1,7 @@  - netstandard1.0;netstandard2.0;net461 + netstandard2.0;net4.6.2;net6.0;net7.0 Neleus.LambdaCompare Neleus.LambdaCompare This project compares two lambda expressions recursively traversing their trees