From dbf6743a904e9361b96c34db5547cdcd9fb2d479 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Fri, 6 Feb 2026 16:25:04 +0500 Subject: [PATCH 1/7] More tests of SertializableExpressions --- .../Linq/SerializableExpressionsTest.cs | 639 +++++++++++++++++- 1 file changed, 603 insertions(+), 36 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Core/Linq/SerializableExpressionsTest.cs b/Orm/Xtensive.Orm.Tests.Core/Linq/SerializableExpressionsTest.cs index 3be10691f4..68286b3226 100644 --- a/Orm/Xtensive.Orm.Tests.Core/Linq/SerializableExpressionsTest.cs +++ b/Orm/Xtensive.Orm.Tests.Core/Linq/SerializableExpressionsTest.cs @@ -15,45 +15,282 @@ using Xtensive.Linq; using Xtensive.Linq.SerializableExpressions; using System.Collections.Generic; +using System.Linq.Expressions; namespace Xtensive.Orm.Tests.Core.Linq { [TestFixture] public class SerializableExpressionsTest : ExpressionTestBase { + #region Nested Types + [Serializable] + internal class Foo + { + public int IntField; + public int IntProperty { get; set; } + public List ListProperty { get; set; } + + public Foo() { } + } + + [Serializable] + internal class Address + { + public string City { get; set; } + public string ZipCode { get; set; } + + public Address() { } + + public Address(string city, string zipCode) + { + City = city; + ZipCode = zipCode; + } + } + + [Serializable] + internal class Person + { + public Address HomeAddress { get; set; } + public string Name { get; set; } + } + #endregion + + public IEnumerable SerializableExpressionTypes { get { - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableBinaryExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableConditionalExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableConstantExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableElementInit); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableInvocationExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableLambdaExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableListInitExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableMemberAssignment); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableMemberBinding); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableMemberExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableMemberInitExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableMemberListBinding); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableMemberMemberBinding); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableMethodCallExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableNewArrayExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableNewExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableParameterExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableTypeBinaryExpression); - yield return typeof(Xtensive.Linq.SerializableExpressions.SerializableUnaryExpression); - } - } - - [Test] - public void ConvertTest() + yield return typeof(SerializableBinaryExpression); + yield return typeof(SerializableConditionalExpression); + yield return typeof(SerializableConstantExpression); + yield return typeof(SerializableElementInit); + yield return typeof(SerializableExpression); + yield return typeof(SerializableInvocationExpression); + yield return typeof(SerializableLambdaExpression); + yield return typeof(SerializableListInitExpression); + yield return typeof(SerializableMemberAssignment); + yield return typeof(SerializableMemberBinding); + yield return typeof(SerializableMemberExpression); + yield return typeof(SerializableMemberInitExpression); + yield return typeof(SerializableMemberListBinding); + yield return typeof(SerializableMemberMemberBinding); + yield return typeof(SerializableMethodCallExpression); + yield return typeof(SerializableNewArrayExpression); + yield return typeof(SerializableNewExpression); + yield return typeof(SerializableParameterExpression); + yield return typeof(SerializableTypeBinaryExpression); + yield return typeof(SerializableUnaryExpression); + } + } + + #region Test Data + + public ConstantExpression[] ConstantExpressions { get; private set; } + public DefaultExpression[] DefaultExpressions { get; private set; } + public ParameterExpression[] ParameterExpressions { get; private set; } + public ParameterExpression[] VariableExpressions { get; private set; } + public BinaryExpression[] BinaryExpressions { get; private set; } + public ConditionalExpression[] ConditionalExpressions { get; private set; } + public InvocationExpression[] InvocationExpressions { get; private set; } + public ListInitExpression[] ListInitExpressions { get; private set; } + public MemberExpression[] MemberExpressions { get; private set; } + public MemberInitExpression[] MemberInitExpressions { get; private set; } + public MethodCallExpression[] MethodCallExpressions { get; private set; } + public NewArrayExpression[] NewArrayExpressions { get; private set; } + public TypeBinaryExpression[] TypeBinaryExpressions { get; private set; } + public UnaryExpression[] UnaryExpressions { get; private set; } + public NewExpression[] NewExpressions { get; private set; } + + #endregion + + public override void TestFixtureSetUp() + { + base.TestFixtureSetUp(); + ConstantExpressions = GetTestConstantExpressions(); + DefaultExpressions = GetTestDefaultExpressions(); + ParameterExpressions = GetTestParameterExpressions(); + VariableExpressions = GetTestVariableExpressions(); + BinaryExpressions = GetTestBinaryExpressions(); + ConditionalExpressions = GetTestConditionalExpressions(); + InvocationExpressions = GetTestInvocationExpressions(); + ListInitExpressions = GetTestListInitExpressions(); + MemberExpressions = GetTestMemberExpressions(); + NewExpressions = GetTestNewExpressions(); + MemberInitExpressions = GetTestMemberInitExressions(); + MethodCallExpressions = GetTestMethodCallExpressions(); + NewArrayExpressions = GetTestNewArrayExpressions(); + TypeBinaryExpressions = GetTestTypeBinaryExpressions(); + UnaryExpressions = GetTestUnaryExpressions(); + } + + + [Test] + public void ConstantExpressionTest() + { + foreach(var origin in ConstantExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void DefaultExpressionTest() + { + foreach (var origin in DefaultExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void ParameterExpressionTest() + { + foreach (var origin in ParameterExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + + foreach (var origin in VariableExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void BinaryExpressionTest() + { + foreach (var origin in BinaryExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void ConditionalExpressionTest() + { + foreach (var origin in ConditionalExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void ListInitExpressionTest() + { + foreach (var origin in ListInitExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void MemberExpressionTest() + { + foreach (var origin in MemberExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void NewExpressionTest() + { + foreach (var origin in NewExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void MemberInitExpressionTest() + { + foreach (var origin in MemberInitExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void MethodCalllExpressionTest() + { + foreach (var origin in MethodCallExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void NewArrayExpressionTest() { + foreach (var origin in NewArrayExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void TypeBinaryExpressionTest() { + foreach (var origin in TypeBinaryExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void UnaryExpressionsTest() + { + foreach (var origin in UnaryExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + + [Test] + public void InvocationExpressionTest() + { + foreach (var origin in InvocationExpressions) { + Console.WriteLine(origin.ToString(true)); + var converted = origin.ToSerializableExpression().ToExpression(); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); + Console.WriteLine("OK"); + } + } + + [Test] + public void LambdaExpressionTest() { foreach (var origin in Expressions) { Console.WriteLine(origin.ToString(true)); var converted = origin.ToSerializableExpression().ToExpression(); - Assert.AreEqual(origin.ToExpressionTree(), converted.ToExpressionTree()); + Assert.That(converted.ToExpressionTree(), Is.EqualTo(origin.ToExpressionTree())); Console.WriteLine("OK"); } } @@ -61,7 +298,7 @@ public void ConvertTest() [Test] public void BinaryFormatterSerializeTest() { - RunSerializeTest(new BinaryFormatter()); + RunSerializeTest(new BinaryFormatter(), Expressions); } [Test] @@ -71,39 +308,48 @@ public void NetDataContractSerializeTest() var settings = new DataContractSerializerSettings(); settings.KnownTypes = list; settings.PreserveObjectReferences = true; - RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings)); + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), Expressions); } - private void RunSerializeTest(XmlObjectSerializer serializer) + private void RunSerializeTest(XmlObjectSerializer serializer, IEnumerable expressions) { using (var stream = new MemoryStream()) { - foreach (var expression in Expressions) { + foreach (var expression in expressions) { Console.WriteLine(expression.ToString(true)); serializer.WriteObject(stream, expression.ToSerializableExpression()); - stream.Seek(0, SeekOrigin.Begin); + _ = stream.Seek(0, SeekOrigin.Begin); var serialized = (SerializableExpression) serializer.ReadObject(stream); stream.SetLength(0); - Assert.AreEqual(expression.ToExpressionTree(), serialized.ToExpression().ToExpressionTree()); + Assert.That(serialized.ToExpression().ToExpressionTree(), Is.EqualTo(expression.ToExpressionTree())); Console.WriteLine("OK"); } } } - private void RunSerializeTest(IFormatter serializer) + private void RunSerializeTest(IFormatter serializer, IEnumerable expressions) { using (var stream = new MemoryStream()) { - foreach (var expression in Expressions) { + foreach (var expression in expressions) { Console.WriteLine(expression.ToString(true)); serializer.Serialize(stream, expression.ToSerializableExpression()); - stream.Seek(0, SeekOrigin.Begin); + _ = stream.Seek(0, SeekOrigin.Begin); var serialized = (SerializableExpression) serializer.Deserialize(stream); stream.SetLength(0); - Assert.AreEqual(expression.ToExpressionTree(), serialized.ToExpression().ToExpressionTree()); + Assert.That(serialized.ToExpression().ToExpressionTree(), Is.EqualTo(expression.ToExpressionTree())); Console.WriteLine("OK"); } } } + //private static T GetClone(T testExp, IEnumerable types) + // where T : Expression + //{ + // var serializable = testExp.ToSerializableExpression(); + // var clone = Cloner.CloneViaDataContractSerializer(serializable, types, out var serializedVersion); + // var native = (T) new SerializableExpressionToExpressionConverter(clone).Convert(); + // return native; + //} + #region Performance test private const int warmUpOperationCount = 10; @@ -159,5 +405,326 @@ private static IDisposable CreateMeasurement(bool warmUp, string name, int opera } #endregion + + #region Test Data initializers + private ConstantExpression[] GetTestConstantExpressions() + { + return new ConstantExpression[] { + Expression.Constant(false, typeof(bool)), + Expression.Constant(true, typeof(bool)), + Expression.Constant((byte) 64, typeof(byte)), + Expression.Constant((sbyte) -64, typeof(sbyte)), + Expression.Constant((short) -128, typeof(short)), + Expression.Constant((ushort) 128, typeof(ushort)), + Expression.Constant((int) -256, typeof(int)), + Expression.Constant((uint) 256, typeof(uint)), + Expression.Constant((long) -512, typeof(long)), + Expression.Constant((ulong) 512, typeof(ulong)), + Expression.Constant((float) 6.283185307f, typeof(float)), + Expression.Constant((double) 6.283185307179586476925, typeof(double)), + Expression.Constant((decimal) 6.2895762354m, typeof(decimal)), + Expression.Constant(TimeSpan.FromSeconds(1024), typeof(TimeSpan)), + Expression.Constant(new DateTime(2018, 9, 27, 18, 55, 56), typeof(DateTime)), + Expression.Constant(null, typeof(string)), + Expression.Constant(string.Empty, typeof(string)), + Expression.Constant("DBC", typeof(string)), + Expression.Constant(new int[] { 1, 2, 3 }), + Expression.Constant(new string[] { "1", "2", "3" }) + }; + } + + private DefaultExpression[] GetTestDefaultExpressions() + { + return new[] { + Expression.Default(typeof(bool)), + Expression.Default(typeof(byte)), + Expression.Default(typeof(sbyte)), + Expression.Default(typeof(short)), + Expression.Default(typeof(ushort)), + Expression.Default(typeof(int)), + Expression.Default(typeof(uint)), + Expression.Default(typeof(long)), + Expression.Default(typeof(ulong)), + Expression.Default(typeof(float)), + Expression.Default(typeof(double)), + Expression.Default(typeof(decimal)), + Expression.Default(typeof(TimeSpan)), + Expression.Default(typeof(DateTime)), + Expression.Default(typeof(string)), + }; + } + + private ParameterExpression[] GetTestParameterExpressions() + { + return new[] { + Expression.Parameter(typeof(bool)), Expression.Parameter(typeof(bool), "boolP"), + Expression.Parameter(typeof(byte)), Expression.Parameter(typeof(byte), "byteP"), + Expression.Parameter(typeof(sbyte)), Expression.Parameter(typeof(sbyte), "sbyteP"), + Expression.Parameter(typeof(short)), Expression.Parameter(typeof(short), "shortP"), + Expression.Parameter(typeof(ushort)), Expression.Parameter(typeof(ushort), "ushortP"), + Expression.Parameter(typeof(int)), Expression.Parameter(typeof(int), "intP"), + Expression.Parameter(typeof(uint)), Expression.Parameter(typeof(uint), "uintP"), + Expression.Parameter(typeof(long)), Expression.Parameter(typeof(long), "longP"), + Expression.Parameter(typeof(ulong)), Expression.Parameter(typeof(ulong), "ulongP"), + Expression.Parameter(typeof(float)), Expression.Parameter(typeof(float), "floatP"), + Expression.Parameter(typeof(double)), Expression.Parameter(typeof(double), "doubleP"), + Expression.Parameter(typeof(decimal)), Expression.Parameter(typeof(decimal), "decimalP"), + Expression.Parameter(typeof(TimeSpan)), Expression.Parameter(typeof(TimeSpan), "timespanP"), + Expression.Parameter(typeof(DateTime)), Expression.Parameter(typeof(DateTime), "datetimeP"), + Expression.Parameter(typeof(string)), Expression.Parameter(typeof(string), "stringP"), + }; + } + + private ParameterExpression[] GetTestVariableExpressions() + { + return new[] { + Expression.Variable(typeof(bool)), Expression.Variable(typeof(bool), "boolP"), + Expression.Variable(typeof(byte)), Expression.Variable(typeof(byte), "byteP"), + Expression.Variable(typeof(sbyte)), Expression.Variable(typeof(sbyte), "sbyteP"), + Expression.Variable(typeof(short)), Expression.Variable(typeof(short), "shortP"), + Expression.Variable(typeof(ushort)), Expression.Variable(typeof(ushort), "ushortP"), + Expression.Variable(typeof(int)), Expression.Variable(typeof(int), "intP"), + Expression.Variable(typeof(uint)), Expression.Variable(typeof(uint), "uintP"), + Expression.Variable(typeof(long)), Expression.Variable(typeof(long), "longP"), + Expression.Variable(typeof(ulong)), Expression.Variable(typeof(ulong), "ulongP"), + Expression.Variable(typeof(float)), Expression.Variable(typeof(float), "floatP"), + Expression.Variable(typeof(double)), Expression.Variable(typeof(double), "doubleP"), + Expression.Variable(typeof(decimal)), Expression.Variable(typeof(decimal), "decimalP"), + Expression.Variable(typeof(TimeSpan)), Expression.Variable(typeof(TimeSpan), "timespanP"), + Expression.Variable(typeof(DateTime)), Expression.Variable(typeof(DateTime), "datetimeP"), + Expression.Variable(typeof(string)), Expression.Variable(typeof(string), "stringP"), + }; + } + + private BinaryExpression[] GetTestBinaryExpressions() + { + return new[] { + Expression.Add(Expression.Constant(1), Expression.Constant(2)), + Expression.AddChecked(Expression.Constant(2), Expression.Constant(3)), + Expression.Subtract(Expression.Constant(4), Expression.Constant(2)), + Expression.SubtractChecked(Expression.Constant(5), Expression.Constant(6)), + Expression.Divide(Expression.Constant(2), Expression.Constant(1)), + Expression.Multiply(Expression.Constant(3), Expression.Constant(5)), + Expression.MultiplyChecked(Expression.Constant(4), Expression.Constant(4)), + Expression.Modulo(Expression.Constant(5), Expression.Constant(2)), + Expression.Power(Expression.Constant(2.0), Expression.Constant(4.0)), + + Expression.And(Expression.Constant(10), Expression.Constant(6)), + Expression.Or(Expression.Constant(10), Expression.Constant(6)), + Expression.ExclusiveOr(Expression.Constant(10), Expression.Constant(6)), + Expression.LeftShift(Expression.Constant(256), Expression.Constant(2)), + Expression.RightShift(Expression.Constant(256), Expression.Constant(2)), + + Expression.Equal(Expression.Constant(256), Expression.Constant(2)), + Expression.NotEqual(Expression.Constant(256), Expression.Constant(2)), + Expression.GreaterThan(Expression.Constant(256), Expression.Constant(2)), + Expression.GreaterThanOrEqual(Expression.Constant(256), Expression.Constant(2)), + Expression.LessThan(Expression.Constant(256), Expression.Constant(2)), + Expression.LessThanOrEqual(Expression.Constant(256), Expression.Constant(2)), + + Expression.AndAlso(Expression.Equal(Expression.Constant(256), Expression.Constant(2)), Expression.NotEqual(Expression.Constant(256), Expression.Constant(3))), + Expression.OrElse(Expression.Equal(Expression.Constant(256), Expression.Constant(2)), Expression.NotEqual(Expression.Constant(256), Expression.Constant(3))), + + Expression.Assign(Expression.Variable(typeof(int), "addAssignVar"), Expression.Constant(111)), + + Expression.Coalesce(Expression.Constant("abc"), Expression.Constant("default")), + Expression.ArrayIndex(Expression.Constant( new[] { 2, 8, 7 }), Expression.Constant(2)) + }; + } + + private ConditionalExpression[] GetTestConditionalExpressions() + { + return new[] { + Expression.Condition( + Expression.Equal(Expression.Constant(12), Expression.Constant(12)), + Expression.Constant(222), + Expression.Constant(333)), + Expression.IfThen( + Expression.Equal(Expression.Constant(12), Expression.Constant(12)), + Expression.Constant(222)), + Expression.IfThenElse( + Expression.Equal(Expression.Constant(14), Expression.Constant(14)), + Expression.Constant(222), + Expression.Constant(333)) + }; + } + + private InvocationExpression[] GetTestInvocationExpressions() + { + Expression> largeSumTest = (num1, num2) => (num1 + num2) > 1000; + return new[] { + Expression.Invoke( + largeSumTest, + Expression.Constant(539), + Expression.Constant(281)) + }; + } + + private ListInitExpression[] GetTestListInitExpressions() + { + var listType = typeof(List); + var constructor = listType.GetConstructor(Type.EmptyTypes); + var newListExpression = Expression.New(constructor); + var addMethod = listType.GetMethod("Add"); + return new[] { + Expression.ListInit( + newListExpression, + Expression.ElementInit(addMethod, Expression.Constant("Apple", typeof(string))), + Expression.ElementInit(addMethod, Expression.Constant("Banana", typeof(string))), + Expression.ElementInit(addMethod, Expression.Constant("Cherry", typeof(string))) + ) + }; + } + + private MemberExpression[] GetTestMemberExpressions() + { + var fooType = typeof(Foo); + var prop = fooType.GetProperty(nameof(Foo.IntProperty)); + var field = fooType.GetField(nameof(Foo.IntField)); + + var instance = new Foo() { IntField = 123, IntProperty = 234 }; + + var instanceExp = Expression.Constant(instance); + + return new[] { + Expression.Field(instanceExp, nameof(Foo.IntField)), + Expression.Field(instanceExp, field), + Expression.Property(instanceExp, nameof(Foo.IntProperty)), + Expression.Property(instanceExp, prop), + }; + } + + private NewExpression[] GetTestNewExpressions() + { + var addressType = typeof(Address); + var addressCtor1 = addressType.GetConstructor(Type.EmptyTypes); + var addressCtor2 = addressType.GetConstructor(new Type[] { typeof(string), typeof(string) }); + + var cityProp = addressType.GetProperty("City"); + var zipCodeProp = addressType.GetProperty("ZipCode"); + + var dateTimeType = typeof(DateTime); + var dateTimeCtor1 = dateTimeType.GetConstructor(new[] { typeof(long) }); + var dateTimeCtor2 = dateTimeType.GetConstructor(new[] { typeof(int), typeof(int), typeof(int) }); + + return new[] { + Expression.New(addressCtor1), + Expression.New(addressCtor2, Expression.Constant("City"), Expression.Constant("ZipCode")), + Expression.New(dateTimeCtor1, Expression.Constant(1224585425L)), + Expression.New(dateTimeCtor2, Expression.Constant(21), Expression.Constant(24), Expression.Constant(26)) + }; + } + + private MemberInitExpression[] GetTestMemberInitExressions() + { + var listType = typeof(List); + var constructor = listType.GetConstructor(Type.EmptyTypes); + var newListExpression = Expression.New(constructor); + var addMethod = listType.GetMethod("Add"); + + var fooType = typeof(Foo); + var prop = fooType.GetProperty(nameof(Foo.IntProperty)); + var field = fooType.GetField(nameof(Foo.IntField)); + + var parameter = Expression.Parameter(typeof(int), "i"); + var newExpression1 = Expression.New(fooType); + var binding = Expression.Bind(prop, parameter); + + var init1 = Expression.ElementInit(addMethod, Expression.Constant("Hello")); + var init2 = Expression.ElementInit(addMethod, Expression.Constant("World")); + + var messagesProperty = fooType.GetProperty(nameof(Foo.ListProperty)); + MemberListBinding listBinding = Expression.ListBind(messagesProperty, init1, init2); + NewExpression newExpression2 = Expression.New(fooType); + + var personType = typeof(Person); + var addressType = typeof(Address); + + var homeAddressMember = personType.GetProperty(nameof(Person.HomeAddress)); + var nameMember = personType.GetProperty(nameof(Person.Name)); + var cityMember = addressType.GetProperty(nameof(Address.City)); + var zipCodeMember = addressType.GetProperty(nameof(Address.ZipCode)); + + var cityAssignment = Expression.Bind(cityMember, Expression.Constant("New York")); + var zipCodeAssignment = Expression.Bind(zipCodeMember, Expression.Constant("10001")); + + var addressBinding = Expression.MemberBind(homeAddressMember, cityAssignment, zipCodeAssignment); + var nameAssignment = Expression.Bind(nameMember, Expression.Constant("John Doe")); + + var newPerson = Expression.New(personType); + + return new[] { + Expression.MemberInit(newExpression1, new[] { binding }), + Expression.MemberInit(newExpression2, new MemberBinding[] { listBinding }), + Expression.MemberInit(newPerson, addressBinding, nameAssignment) + }; + } + + private MethodCallExpression[] GetTestMethodCallExpressions() + { + var param1 = Expression.Constant(2, typeof(int)); + var param2 = Expression.Constant(3L, typeof(long)); + var thisType = this.GetType(); + + var nonpublicMethods = thisType.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); + var publicMethods = thisType.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); + + var staticLocalMethod = nonpublicMethods + .FirstOrDefault(m => m.Name.StartsWith($"<{nameof(GetTestMethodCallExpressions)}>g__{nameof(SDummyMethod)}")); + + var publicStaticMethod = publicMethods + .FirstOrDefault(m => m.Name.StartsWith($"{nameof(SFromCloneMethodCallExpressionDummy)}")); + + var privateStaticMethod = nonpublicMethods + .FirstOrDefault(m => m.Name.StartsWith($"{nameof(PSFromCloneMethodCallExpressionDummy)}")); + + return new[] { + Expression.Call(publicStaticMethod, param1, param2), + Expression.Call(privateStaticMethod, param1, param2), + Expression.Call(staticLocalMethod, param1, param2), + Expression.ArrayIndex(Expression.Constant(new[] { 8, 9, 10 }), new[] { Expression.Constant(1) }), + }; + + static void SDummyMethod(int a, long b) + { } + } + + private NewArrayExpression[] GetTestNewArrayExpressions() + { + return new[] { + Expression.NewArrayBounds(typeof(string), Expression.Constant(1)), + Expression.NewArrayBounds(typeof(string), Expression.Constant(2), Expression.Constant(3)), + Expression.NewArrayInit(typeof(string), Expression.Constant("ABC"), Expression.Constant("DEF")), + }; + } + + private TypeBinaryExpression[] GetTestTypeBinaryExpressions() + { + return new[] { + Expression.TypeIs(Expression.Constant(DateTime.UtcNow), typeof(DateTime)), + Expression.TypeEqual(Expression.Constant(DateTime.UtcNow), typeof(DateTime)) + }; + } + + private UnaryExpression[] GetTestUnaryExpressions() + { + return new[] { + Expression.ArrayLength(Expression.Constant(new int[] { 1,2,3 })), + Expression.Convert(Expression.Constant(23), typeof(long)), + Expression.ConvertChecked(Expression.Constant(23), typeof(long)), + Expression.Decrement(Expression.Constant(24)), + Expression.Increment(Expression.Constant(25)), + Expression.IsFalse(Expression.Constant(true)), + Expression.IsTrue(Expression.Constant(true)), + Expression.Negate(Expression.Constant(-25)), + Expression.NegateChecked(Expression.Constant(-25)), + Expression.Not(Expression.Constant(true)), + Expression.OnesComplement(Expression.Constant(123)), + }; + } + + public static void SFromCloneMethodCallExpressionDummy(int a, long b) { } + private static void PSFromCloneMethodCallExpressionDummy(int a, long b) { } } + #endregion } \ No newline at end of file From a829349f39ced03558e8868cd3fad740f54a31c7 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Fri, 6 Feb 2026 16:27:08 +0500 Subject: [PATCH 2/7] SerializableExpression -> Expression: Add support for DefaultExpression --- .../SerializableExpressionToExpressionConverter.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/SerializableExpressionToExpressionConverter.cs b/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/SerializableExpressionToExpressionConverter.cs index 06c5ce318b..0df35f3d1d 100644 --- a/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/SerializableExpressionToExpressionConverter.cs +++ b/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/SerializableExpressionToExpressionConverter.cs @@ -79,6 +79,9 @@ private Expression Visit(SerializableExpression e) case ExpressionType.Constant: result = VisitConstant((SerializableConstantExpression)e); break; + case ExpressionType.Default: + result = VisitDefault((SerializableDefaultExpression)e); + break; case ExpressionType.Parameter: result = VisitParameter((SerializableParameterExpression)e); break; @@ -135,6 +138,11 @@ private Expression VisitConstant(SerializableConstantExpression c) return Expression.Constant(c.Value, c.Type); } + private Expression VisitDefault(SerializableDefaultExpression d) + { + return Expression.Default(d.Type); + } + private Expression VisitConditional(SerializableConditionalExpression c) { return Expression.Condition(Visit(c.Test), Visit(c.IfTrue), Visit(c.IfFalse)); From 1d0e0c86721c68ad6dd45f0e34d4c698da53599e Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Fri, 6 Feb 2026 16:56:18 +0500 Subject: [PATCH 3/7] Extended list of convertable serializable expressions --- ...SerializableExpressionToExpressionConverter.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/SerializableExpressionToExpressionConverter.cs b/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/SerializableExpressionToExpressionConverter.cs index 0df35f3d1d..e44370dd74 100644 --- a/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/SerializableExpressionToExpressionConverter.cs +++ b/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/SerializableExpressionToExpressionConverter.cs @@ -43,6 +43,11 @@ private Expression Visit(SerializableExpression e) case ExpressionType.ArrayLength: case ExpressionType.Quote: case ExpressionType.TypeAs: + case ExpressionType.Decrement: + case ExpressionType.Increment: + case ExpressionType.IsFalse: + case ExpressionType.IsTrue: + case ExpressionType.OnesComplement: result = VisitUnary((SerializableUnaryExpression)e); break; case ExpressionType.Add: @@ -68,11 +73,16 @@ private Expression Visit(SerializableExpression e) case ExpressionType.RightShift: case ExpressionType.LeftShift: case ExpressionType.ExclusiveOr: + case ExpressionType.Power: + case ExpressionType.Assign: result = VisitBinary((SerializableBinaryExpression)e); break; case ExpressionType.TypeIs: result = VisitTypeIs((SerializableTypeBinaryExpression)e); break; + case ExpressionType.TypeEqual: + result = VisitTypeEqual((SerializableTypeBinaryExpression) e); + break; case ExpressionType.Conditional: result = VisitConditional((SerializableConditionalExpression)e); break; @@ -133,6 +143,11 @@ private Expression VisitTypeIs(SerializableTypeBinaryExpression tb) return Expression.TypeIs(Visit(tb.Expression), tb.TypeOperand); } + private Expression VisitTypeEqual(SerializableTypeBinaryExpression tb) + { + return Expression.TypeEqual(Visit(tb.Expression), tb.TypeOperand); + } + private Expression VisitConstant(SerializableConstantExpression c) { return Expression.Constant(c.Value, c.Type); From 5621c98e0f854aac69cc8ce7522d85300c95923d Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 9 Feb 2026 18:55:20 +0500 Subject: [PATCH 4/7] Add tests of clone via serialization --- .../Linq/SerializableExpressionsTest.cs | 368 ++++++++++++++++-- 1 file changed, 336 insertions(+), 32 deletions(-) diff --git a/Orm/Xtensive.Orm.Tests.Core/Linq/SerializableExpressionsTest.cs b/Orm/Xtensive.Orm.Tests.Core/Linq/SerializableExpressionsTest.cs index 68286b3226..1b7e1e9c6d 100644 --- a/Orm/Xtensive.Orm.Tests.Core/Linq/SerializableExpressionsTest.cs +++ b/Orm/Xtensive.Orm.Tests.Core/Linq/SerializableExpressionsTest.cs @@ -16,6 +16,7 @@ using Xtensive.Linq.SerializableExpressions; using System.Collections.Generic; using System.Linq.Expressions; +using Xtensive.Reflection; namespace Xtensive.Orm.Tests.Core.Linq { @@ -63,6 +64,7 @@ public IEnumerable SerializableExpressionTypes yield return typeof(SerializableBinaryExpression); yield return typeof(SerializableConditionalExpression); yield return typeof(SerializableConstantExpression); + yield return typeof(SerializableDefaultExpression); yield return typeof(SerializableElementInit); yield return typeof(SerializableExpression); yield return typeof(SerializableInvocationExpression); @@ -135,6 +137,24 @@ public void ConstantExpressionTest() } } + [Test] + public void ConstantExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), ConstantExpressions); + } + + [Test] + public void ConstantExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list.Union(new[] { typeof(int[]), typeof(string[]) }), + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), ConstantExpressions); + } + + [Test] public void DefaultExpressionTest() { @@ -146,6 +166,23 @@ public void DefaultExpressionTest() } } + [Test] + public void DefaultExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), DefaultExpressions); + } + + [Test] + public void DefaultExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list, + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), DefaultExpressions); + } + [Test] public void ParameterExpressionTest() { @@ -164,6 +201,25 @@ public void ParameterExpressionTest() } } + [Test] + public void ParameterExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), ParameterExpressions); + RunSerializeTest(new BinaryFormatter(), VariableExpressions); + } + + [Test] + public void ParameterExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list, + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), ParameterExpressions); + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), VariableExpressions); + } + [Test] public void BinaryExpressionTest() { @@ -175,6 +231,23 @@ public void BinaryExpressionTest() } } + [Test] + public void BinaryExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), BinaryExpressions); + } + + [Test] + public void BinaryExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list.Union(new[] { typeof(int[]) }), + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), BinaryExpressions); + } + [Test] public void ConditionalExpressionTest() { @@ -186,6 +259,23 @@ public void ConditionalExpressionTest() } } + [Test] + public void ConditionalExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), ConditionalExpressions); + } + + [Test] + public void ConditionalExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list, + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), ConditionalExpressions); + } + [Test] public void ListInitExpressionTest() { @@ -197,6 +287,23 @@ public void ListInitExpressionTest() } } + [Test] + public void ListInitExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), ListInitExpressions); + } + + [Test] + public void ListInitExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list, + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), ListInitExpressions); + } + [Test] public void MemberExpressionTest() { @@ -208,6 +315,23 @@ public void MemberExpressionTest() } } + [Test] + public void MemberExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), MemberExpressions); + } + + [Test] + public void MemberExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list.Union(new[] { typeof(Foo) }), + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), MemberExpressions); + } + [Test] public void NewExpressionTest() { @@ -219,6 +343,23 @@ public void NewExpressionTest() } } + [Test] + public void NewExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), NewExpressions); + } + + [Test] + public void NewExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list, + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), NewExpressions); + } + [Test] public void MemberInitExpressionTest() { @@ -231,7 +372,24 @@ public void MemberInitExpressionTest() } [Test] - public void MethodCalllExpressionTest() + public void MemberInitExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), MemberInitExpressions); + } + + [Test] + public void MemberInitExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list, + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), MemberInitExpressions); + } + + [Test] + public void MethodCallExpressionTest() { foreach (var origin in MethodCallExpressions) { Console.WriteLine(origin.ToString(true)); @@ -241,6 +399,23 @@ public void MethodCalllExpressionTest() } } + [Test] + public void MethodCallExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), MethodCallExpressions); + } + + [Test] + public void MethodCallExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list.Union(new[] { typeof(int[]) }), + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), MethodCallExpressions); + } + [Test] public void NewArrayExpressionTest() { foreach (var origin in NewArrayExpressions) { @@ -251,6 +426,23 @@ public void NewArrayExpressionTest() { } } + [Test] + public void NewArrayExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), NewArrayExpressions); + } + + [Test] + public void NewArrayExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list, + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), NewArrayExpressions); + } + [Test] public void TypeBinaryExpressionTest() { foreach (var origin in TypeBinaryExpressions) { @@ -261,6 +453,23 @@ public void TypeBinaryExpressionTest() { } } + [Test] + public void TypeBinaryExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), TypeBinaryExpressions); + } + + [Test] + public void TypeBinaryExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list, + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), TypeBinaryExpressions); + } + [Test] public void UnaryExpressionsTest() { @@ -272,6 +481,22 @@ public void UnaryExpressionsTest() } } + [Test] + public void UnaryExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), UnaryExpressions); + } + + [Test] + public void UnaryExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list.Union(new[] { typeof(int[]) }), + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), UnaryExpressions); + } [Test] public void InvocationExpressionTest() @@ -284,6 +509,23 @@ public void InvocationExpressionTest() } } + [Test] + public void InvocationExpressionBinaryCloneTest() + { + RunSerializeTest(new BinaryFormatter(), InvocationExpressions); + } + + [Test] + public void InvocationExpressionNonBinaryCloneTest() + { + var list = SerializableExpressionTypes.ToList(); + var settings = new DataContractSerializerSettings { + KnownTypes = list, + PreserveObjectReferences = true + }; + RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), InvocationExpressions); + } + [Test] public void LambdaExpressionTest() { @@ -296,18 +538,19 @@ public void LambdaExpressionTest() } [Test] - public void BinaryFormatterSerializeTest() + public void LambdaExpressionBinaryCloneTest() { RunSerializeTest(new BinaryFormatter(), Expressions); } [Test] - public void NetDataContractSerializeTest() + public void LambdaExpressionNonBinaryCloneTest() { var list = SerializableExpressionTypes.ToList(); - var settings = new DataContractSerializerSettings(); - settings.KnownTypes = list; - settings.PreserveObjectReferences = true; + var settings = new DataContractSerializerSettings { + KnownTypes = list, + PreserveObjectReferences = true + }; RunSerializeTest(new DataContractSerializer(typeof(SerializableExpression), settings), Expressions); } @@ -320,7 +563,7 @@ private void RunSerializeTest(XmlObjectSerializer serializer, IEnumerable exp _ = stream.Seek(0, SeekOrigin.Begin); var serialized = (SerializableExpression) serializer.Deserialize(stream); stream.SetLength(0); - Assert.That(serialized.ToExpression().ToExpressionTree(), Is.EqualTo(expression.ToExpressionTree())); + CompareExpressions(expression, serialized.ToExpression()); Console.WriteLine("OK"); } } } - //private static T GetClone(T testExp, IEnumerable types) - // where T : Expression - //{ - // var serializable = testExp.ToSerializableExpression(); - // var clone = Cloner.CloneViaDataContractSerializer(serializable, types, out var serializedVersion); - // var native = (T) new SerializableExpressionToExpressionConverter(clone).Convert(); - // return native; - //} + private void CompareExpressions(Expression expression, Expression serialized) + { + if (expression is ConstantExpression cExpressions) + CompareConstants(cExpressions, (ConstantExpression) serialized); + else if (expression is MemberExpression mExpression) + CompareMemberExpressions(mExpression, (MemberExpression) serialized); + else if (expression is BinaryExpression bExpression && bExpression.NodeType == ExpressionType.ArrayIndex) + CompareArrayIndex(bExpression, (BinaryExpression) serialized); + else if (expression is UnaryExpression uExpression && uExpression.NodeType == ExpressionType.ArrayLength) + CompareArrayLength(uExpression, (UnaryExpression) serialized); + else + Assert.That(serialized.ToExpressionTree(), Is.EqualTo(expression.ToExpressionTree())); + } + + private void CompareMemberExpressions(MemberExpression origin, MemberExpression clone) + { + Assert.That(clone.Type, Is.EqualTo(origin.Type)); + + var instanceOrig = origin.Expression as ConstantExpression; + var instanceClone = clone.Expression as ConstantExpression; + + Assert.That(instanceClone.Type, Is.EqualTo(instanceOrig.Type)); + + var instValueOrig = instanceOrig.Value as Foo; + var instValueClone = instanceClone.Value as Foo; + + Assert.That(instValueClone.IntField, Is.EqualTo(instValueOrig.IntField)); + Assert.That(instValueClone.IntProperty, Is.EqualTo(instValueOrig.IntProperty)); + + Assert.That(clone.Member, Is.EqualTo(origin.Member)); + } + + private void CompareArrayIndex(BinaryExpression origin, BinaryExpression clone) + { + if (origin.NodeType != ExpressionType.ArrayIndex) + throw new ArgumentException(); + + Assert.That(clone.Type, Is.EqualTo(origin.Type)); + + CompareConstants((ConstantExpression) origin.Left, (ConstantExpression) clone.Left); + CompareConstants((ConstantExpression) origin.Right, (ConstantExpression) clone.Right); + + } + + private void CompareArrayLength(UnaryExpression origin, UnaryExpression clone) + { + if (origin.NodeType != ExpressionType.ArrayLength) + throw new ArgumentException(); + + Assert.That(clone.Type, Is.EqualTo(origin.Type)); + + var instanceOrig = (origin.Operand as ConstantExpression); + var instanceClone = (clone.Operand as ConstantExpression); + + CompareConstants(instanceOrig, instanceClone); + } + + private void CompareConstants(ConstantExpression origin, ConstantExpression clone) + { + Assert.That(clone.Type, Is.EqualTo(origin.Type)); + + if (origin.Type.IsArray) { + Assert.That(origin.Value, Is.Not.Null); + Assert.That(clone.Value, Is.Not.Null); + Assert.That(clone.Value, Is.EquivalentTo(origin.Value as Array)); + } + else { + Assert.That(clone.Value, Is.EqualTo(origin.Value)); + } + } #region Performance test @@ -428,8 +733,8 @@ private ConstantExpression[] GetTestConstantExpressions() Expression.Constant(null, typeof(string)), Expression.Constant(string.Empty, typeof(string)), Expression.Constant("DBC", typeof(string)), - Expression.Constant(new int[] { 1, 2, 3 }), - Expression.Constant(new string[] { "1", "2", "3" }) + Expression.Constant(new[] { 1, 2, 3 }), + Expression.Constant(new[] { "1", "2", "3" }) }; } @@ -499,14 +804,14 @@ private ParameterExpression[] GetTestVariableExpressions() private BinaryExpression[] GetTestBinaryExpressions() { return new[] { - Expression.Add(Expression.Constant(1), Expression.Constant(2)), - Expression.AddChecked(Expression.Constant(2), Expression.Constant(3)), - Expression.Subtract(Expression.Constant(4), Expression.Constant(2)), - Expression.SubtractChecked(Expression.Constant(5), Expression.Constant(6)), - Expression.Divide(Expression.Constant(2), Expression.Constant(1)), - Expression.Multiply(Expression.Constant(3), Expression.Constant(5)), - Expression.MultiplyChecked(Expression.Constant(4), Expression.Constant(4)), - Expression.Modulo(Expression.Constant(5), Expression.Constant(2)), + //Expression.Add(Expression.Constant(1), Expression.Constant(2)), + //Expression.AddChecked(Expression.Constant(2), Expression.Constant(3)), + //Expression.Subtract(Expression.Constant(4), Expression.Constant(2)), + //Expression.SubtractChecked(Expression.Constant(5), Expression.Constant(6)), + //Expression.Divide(Expression.Constant(2), Expression.Constant(1)), + //Expression.Multiply(Expression.Constant(3), Expression.Constant(5)), + //Expression.MultiplyChecked(Expression.Constant(4), Expression.Constant(4)), + //Expression.Modulo(Expression.Constant(5), Expression.Constant(2)), Expression.Power(Expression.Constant(2.0), Expression.Constant(4.0)), Expression.And(Expression.Constant(10), Expression.Constant(6)), @@ -537,15 +842,15 @@ private ConditionalExpression[] GetTestConditionalExpressions() return new[] { Expression.Condition( Expression.Equal(Expression.Constant(12), Expression.Constant(12)), - Expression.Constant(222), - Expression.Constant(333)), - Expression.IfThen( - Expression.Equal(Expression.Constant(12), Expression.Constant(12)), + Expression.Constant(111), Expression.Constant(222)), + Expression.IfThen( + Expression.Equal(Expression.Constant(13), Expression.Constant(13)), + Expression.Constant(333)), Expression.IfThenElse( Expression.Equal(Expression.Constant(14), Expression.Constant(14)), Expression.Constant(222), - Expression.Constant(333)) + Expression.Constant(444)) }; } @@ -682,7 +987,6 @@ private MethodCallExpression[] GetTestMethodCallExpressions() Expression.Call(publicStaticMethod, param1, param2), Expression.Call(privateStaticMethod, param1, param2), Expression.Call(staticLocalMethod, param1, param2), - Expression.ArrayIndex(Expression.Constant(new[] { 8, 9, 10 }), new[] { Expression.Constant(1) }), }; static void SDummyMethod(int a, long b) From 65d19a65f0092a05528d2b2ef69ad6c5ef5885cf Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 9 Feb 2026 19:10:20 +0500 Subject: [PATCH 5/7] Extend supported ExpressionTypes by Expression visitors --- .../Linq/ExpressionVisitor{TResult}.cs | 8 ++++++++ .../Linq/Internals/ExpressionComparer.cs | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Orm/Xtensive.Orm/Linq/ExpressionVisitor{TResult}.cs b/Orm/Xtensive.Orm/Linq/ExpressionVisitor{TResult}.cs index b9900b549c..26a4c73984 100644 --- a/Orm/Xtensive.Orm/Linq/ExpressionVisitor{TResult}.cs +++ b/Orm/Xtensive.Orm/Linq/ExpressionVisitor{TResult}.cs @@ -60,6 +60,11 @@ protected virtual TResult Visit(Expression e) case ExpressionType.ArrayLength: case ExpressionType.Quote: case ExpressionType.TypeAs: + case ExpressionType.Decrement: + case ExpressionType.Increment: + case ExpressionType.IsFalse: + case ExpressionType.IsTrue: + case ExpressionType.OnesComplement: result = VisitUnary((UnaryExpression) e); break; case ExpressionType.Add: @@ -85,9 +90,12 @@ protected virtual TResult Visit(Expression e) case ExpressionType.RightShift: case ExpressionType.LeftShift: case ExpressionType.ExclusiveOr: + case ExpressionType.Power: + case ExpressionType.Assign: result = VisitBinary((BinaryExpression) e); break; case ExpressionType.TypeIs: + case ExpressionType.TypeEqual: result = VisitTypeIs((TypeBinaryExpression) e); break; case ExpressionType.Conditional: diff --git a/Orm/Xtensive.Orm/Linq/Internals/ExpressionComparer.cs b/Orm/Xtensive.Orm/Linq/Internals/ExpressionComparer.cs index 1242ce8363..23c597ae1a 100644 --- a/Orm/Xtensive.Orm/Linq/Internals/ExpressionComparer.cs +++ b/Orm/Xtensive.Orm/Linq/Internals/ExpressionComparer.cs @@ -51,6 +51,11 @@ private bool Visit(Expression x, Expression y) case ExpressionType.ArrayLength: case ExpressionType.Quote: case ExpressionType.TypeAs: + case ExpressionType.Decrement: + case ExpressionType.Increment: + case ExpressionType.IsFalse: + case ExpressionType.IsTrue: + case ExpressionType.OnesComplement: return VisitUnary((UnaryExpression) x, (UnaryExpression) y); case ExpressionType.Add: case ExpressionType.AddChecked: @@ -75,13 +80,19 @@ private bool Visit(Expression x, Expression y) case ExpressionType.RightShift: case ExpressionType.LeftShift: case ExpressionType.ExclusiveOr: + case ExpressionType.Power: + case ExpressionType.Assign: return VisitBinary((BinaryExpression) x, (BinaryExpression) y); case ExpressionType.TypeIs: return VisitTypeIs((TypeBinaryExpression) x, (TypeBinaryExpression) y); + case ExpressionType.TypeEqual: + return VisitTypeEqual((TypeBinaryExpression) x, (TypeBinaryExpression) y); case ExpressionType.Conditional: return VisitConditional((ConditionalExpression) x, (ConditionalExpression) y); case ExpressionType.Constant: return VisitConstant((ConstantExpression) x, (ConstantExpression) y); + case ExpressionType.Default: + return true; // types and references are already compared case ExpressionType.Parameter: return VisitParameter((ParameterExpression) x, (ParameterExpression) y); case ExpressionType.MemberAccess: @@ -236,6 +247,11 @@ private bool VisitTypeIs(TypeBinaryExpression x, TypeBinaryExpression y) return x.TypeOperand==y.TypeOperand && Visit(x.Expression, y.Expression); } + private bool VisitTypeEqual(TypeBinaryExpression x, TypeBinaryExpression y) + { + return x.TypeOperand == y.TypeOperand && Visit(x.Expression, y.Expression); + } + private bool VisitBinary(BinaryExpression x, BinaryExpression y) { return x.Method==y.Method && Visit(x.Left, y.Left) && Visit(x.Right, y.Right); From 5dbea0d59572c3f77d395aad3645a19a6b7f3ca3 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 10 Feb 2026 15:46:26 +0500 Subject: [PATCH 6/7] Fix certain problems with expressions - handle DefaultExpression when type is Void and cannot be instantiated - To/FromSerializableForm for methods/members now supports non-public methods/members --- .../Core/Extensions/ExpressionExtensions.cs | 2 +- .../Internals/ExpressionHashCodeCalculator.cs | 2 +- .../Internals/ReflectionExtensions.cs | 19 ++++++++++++++++--- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Orm/Xtensive.Orm/Core/Extensions/ExpressionExtensions.cs b/Orm/Xtensive.Orm/Core/Extensions/ExpressionExtensions.cs index 2196f55ff6..e159d93e1d 100644 --- a/Orm/Xtensive.Orm/Core/Extensions/ExpressionExtensions.cs +++ b/Orm/Xtensive.Orm/Core/Extensions/ExpressionExtensions.cs @@ -135,7 +135,7 @@ public static ConstantExpression ToConstantExpression(this DefaultExpression def /// Object value of default value. public static object GetDefaultValue(this DefaultExpression defaultExpression) { - if (defaultExpression.Type.IsValueType) { + if (defaultExpression.Type != typeof(void) && defaultExpression.Type.IsValueType) { return StructDefaultValues.GetOrAdd( defaultExpression.Type, (type, expr) => { return ((Func) Expression.Lambda(Expression.Convert(expr, WellKnownTypes.Object)).Compile()).Invoke(); }, diff --git a/Orm/Xtensive.Orm/Linq/Internals/ExpressionHashCodeCalculator.cs b/Orm/Xtensive.Orm/Linq/Internals/ExpressionHashCodeCalculator.cs index e1386eae5e..96bac5fffa 100644 --- a/Orm/Xtensive.Orm/Linq/Internals/ExpressionHashCodeCalculator.cs +++ b/Orm/Xtensive.Orm/Linq/Internals/ExpressionHashCodeCalculator.cs @@ -64,7 +64,7 @@ protected override int VisitConstant(ConstantExpression c) protected override int VisitDefault(DefaultExpression d) { if (d.Type.IsValueType) { - return d.GetDefaultValue().GetHashCode(); + return d.GetDefaultValue()?.GetHashCode() ?? NullHashCode; } else { return NullHashCode; diff --git a/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/ReflectionExtensions.cs b/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/ReflectionExtensions.cs index e10da72981..492dd06516 100644 --- a/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/ReflectionExtensions.cs +++ b/Orm/Xtensive.Orm/Linq/SerializableExpressions/Internals/ReflectionExtensions.cs @@ -30,7 +30,7 @@ public static string ToSerializableForm(this MethodInfo method) serializableName += method.ToString(); else serializableName += method.GetGenericMethodDefinition() + Environment.NewLine + - String.Join(Environment.NewLine, + string.Join(Environment.NewLine, method.GetGenericArguments().Select(ty => ty.ToSerializableForm()).ToArray()); return serializableName; } @@ -42,7 +42,13 @@ public static MethodInfo GetMethodFromSerializableForm(this string serializedVal var fullName = SplitString(serializedValue); var name = fullName[1]; - var method = Type.GetType(fullName[0]).GetMethods().First(m => m.ToString() == name); + var type = Type.GetType(fullName[0]); + var publicMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + var method = publicMethods.FirstOrDefault(m => m.ToString() == name); + if (method == null) { + var nonPublicMethods = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + method = nonPublicMethods.First(m => m.ToString() == name); + } if (method.IsGenericMethod) method = method.MakeGenericMethod(fullName.Skip(2).Select(s => GetTypeFromSerializableForm(s)).ToArray()); @@ -64,7 +70,14 @@ public static MemberInfo GetMemberFromSerializableForm(this string serializedVal var fullName = SplitString(serializedValue); var name = fullName[1]; - var member = Type.GetType(fullName[0]).GetMembers().First(m => m.ToString() == name); + var type = Type.GetType(fullName[0]); + var publicMemberss = type.GetMembers(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + var member = publicMemberss.FirstOrDefault(m => m.ToString() == name); + if (member == null) { + var nonPublicMembers = type.GetMembers(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static); + member = nonPublicMembers.First(m => m.ToString() == name); + } + return member; } From b4e4e5c6c3094fbbb2a1fdaa33667bc8c8c63fb6 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Fri, 13 Mar 2026 12:18:35 +0500 Subject: [PATCH 7/7] Improve changelog --- ChangeLog/7.2.1-dev.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/ChangeLog/7.2.1-dev.txt b/ChangeLog/7.2.1-dev.txt index 389cba7a02..bfa4e69ad8 100644 --- a/ChangeLog/7.2.1-dev.txt +++ b/ChangeLog/7.2.1-dev.txt @@ -5,5 +5,6 @@ [main] IPriorityQueue interface became Obsolete [main] EnumerableUtils.One() marked as Obsolete [main] Performance and memory usage improvements +[main] Fixed certain issues connected with SerializableExpressions deserialization [postgresql] Update Npgsql to 9.0.4 [weaver] Updated Mono.Cecil package to v0.11.6, which resolves certain issues of unsynced pdb files