diff --git a/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs b/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs index aa4589d54b8..865f95f73ca 100644 --- a/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs +++ b/src/EFCore.Cosmos/ChangeTracking/Internal/StringDictionaryComparer.cs @@ -15,13 +15,13 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.ChangeTracking.Internal; public sealed class StringDictionaryComparer : ValueComparer, IInfrastructure { private static readonly MethodInfo CompareMethod = typeof(StringDictionaryComparer).GetMethod( - nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(object), typeof(ValueComparer)])!; + nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(object), typeof(Func)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(StringDictionaryComparer).GetMethod( - nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo SnapshotMethod = typeof(StringDictionaryComparer).GetMethod( - nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(ValueComparer)])!; + nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(Func)])!; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -57,9 +57,7 @@ ValueComparer IInfrastructure.Instance CompareMethod, prm1, prm2, -#pragma warning disable EF9100 - elementComparer.ConstructorExpression), -#pragma warning restore EF9100 + elementComparer.EqualsExpression), prm1, prm2); } @@ -74,9 +72,7 @@ private static Expression> GetHashCodeLambda(ValueComparer ele Expression.Convert( prm, typeof(IEnumerable)), -#pragma warning disable EF9100 - elementComparer.ConstructorExpression), -#pragma warning restore EF9100 + elementComparer.HashCodeExpression), prm); } @@ -88,13 +84,11 @@ private static Expression> SnapshotLambda(ValueComparer ele Expression.Call( SnapshotMethod, prm, -#pragma warning disable EF9100 - elementComparer.ConstructorExpression), -#pragma warning restore EF9100 + elementComparer.SnapshotExpression), prm); } - private static bool Compare(object? a, object? b, ValueComparer elementComparer) + private static bool Compare(object? a, object? b, Func elementCompare) { if (ReferenceEquals(a, b)) { @@ -121,7 +115,7 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer) foreach (var pair in aDictionary) { if (!bDictionary.TryGetValue(pair.Key, out var bValue) - || !elementComparer.Equals(pair.Value, bValue)) + || !elementCompare(pair.Value, bValue)) { return false; } @@ -133,17 +127,17 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer) throw new InvalidOperationException( CosmosStrings.BadDictionaryType( (a is IDictionary ? b : a).GetType().ShortDisplayName(), - typeof(IDictionary<,>).MakeGenericType(typeof(string), elementComparer.Type).ShortDisplayName())); + typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(TElement)).ShortDisplayName())); } - private static int GetHashCode(IEnumerable source, ValueComparer elementComparer) + private static int GetHashCode(IEnumerable source, Func elementGetHashCode) { if (source is not IReadOnlyDictionary sourceDictionary) { throw new InvalidOperationException( CosmosStrings.BadDictionaryType( source.GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); } var hash = new HashCode(); @@ -151,26 +145,26 @@ private static int GetHashCode(IEnumerable source, ValueComparer elementComparer foreach (var pair in sourceDictionary) { hash.Add(pair.Key); - hash.Add(pair.Value == null ? 0 : elementComparer.GetHashCode(pair.Value)); + hash.Add(pair.Value == null ? 0 : elementGetHashCode(pair.Value)); } return hash.ToHashCode(); } - private static IReadOnlyDictionary Snapshot(object source, ValueComparer elementComparer) + private static IReadOnlyDictionary Snapshot(object source, Func elementSnapshot) { if (source is not IReadOnlyDictionary sourceDictionary) { throw new InvalidOperationException( CosmosStrings.BadDictionaryType( source.GetType().ShortDisplayName(), - typeof(IDictionary<,>).MakeGenericType(typeof(string), elementComparer.Type).ShortDisplayName())); + typeof(IDictionary<,>).MakeGenericType(typeof(string), typeof(TElement)).ShortDisplayName())); } var snapshot = new Dictionary(); foreach (var pair in sourceDictionary) { - snapshot[pair.Key] = pair.Value == null ? default : (TElement?)elementComparer.Snapshot(pair.Value); + snapshot[pair.Key] = pair.Value == null ? default : (TElement?)elementSnapshot(pair.Value); } return snapshot; diff --git a/src/EFCore/ChangeTracking/Internal/ConvertingValueComparer.cs b/src/EFCore/ChangeTracking/Internal/ConvertingValueComparer.cs new file mode 100644 index 00000000000..f5b58f0e265 --- /dev/null +++ b/src/EFCore/ChangeTracking/Internal/ConvertingValueComparer.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal; + +using static Expression; + +/// +/// A composable value comparer that accepts a value comparer, and exposes it as a value comparer for a base type. +/// Used when a collection comparer over e.g. object[] is needed over a specific element type (e.g. int) +/// +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +public class ConvertingValueComparer : ValueComparer, IInfrastructure +{ + private readonly ValueComparer _valueComparer; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public ConvertingValueComparer(ValueComparer valueComparer) + : base( + CreateEquals(valueComparer), + CreateHashCode(valueComparer), + CreateSnapshot(valueComparer)) + => _valueComparer = valueComparer; + + private static Expression> CreateEquals(ValueComparer valueComparer) + { + var p1 = Parameter(typeof(TTo), "v1"); + var p2 = Parameter(typeof(TTo), "v2"); + + var body = typeof(TTo).IsAssignableFrom(typeof(TFrom)) + ? valueComparer.EqualsExpression.Body + : valueComparer.ExtractEqualsBody( + Convert(p1, typeof(TFrom)), + Convert(p2, typeof(TFrom))); + + return Lambda>( + body, + p1, + p2); + } + + private static Expression> CreateHashCode(ValueComparer valueComparer) + { + var p = Parameter(typeof(TTo), "v"); + + var body = typeof(TTo).IsAssignableFrom(typeof(TFrom)) + ? valueComparer.HashCodeExpression.Body + : valueComparer.ExtractHashCodeBody( + Convert(p, typeof(TFrom))); + + return Lambda>( + body, + p); + } + + private static Expression> CreateSnapshot(ValueComparer valueComparer) + { + var p = Parameter(typeof(TTo), "v"); + + // types must match exactly as we have both covariance and contravariance case here + var body = typeof(TTo) == typeof(TFrom) + ? valueComparer.SnapshotExpression.Body + : Convert( + valueComparer.ExtractSnapshotBody( + Convert(p, typeof(TFrom))), + typeof(TTo)); + + return Lambda>( + body, + p); + } + + ValueComparer IInfrastructure.Instance + => _valueComparer; +} diff --git a/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs b/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs index d5a17ac236f..bf199e20f08 100644 --- a/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs +++ b/src/EFCore/ChangeTracking/Internal/ValueComparerExtensions.cs @@ -25,4 +25,30 @@ public static class ValueComparerExtensions : (ValueComparer)Activator.CreateInstance( typeof(NullableValueComparer<>).MakeGenericType(valueComparer.Type), valueComparer)!; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static ValueComparer? ComposeConversion(this ValueComparer? valueComparer, Type targetClrType) + { + if (valueComparer is null || valueComparer.Type == targetClrType) + { + return valueComparer; + } + + if (targetClrType.IsNullableValueType() && !valueComparer.Type.IsNullableValueType() + && targetClrType.UnwrapNullableType() == valueComparer.Type) + { + return (ValueComparer)Activator.CreateInstance( + typeof(NullableValueComparer<>).MakeGenericType(valueComparer.Type), + valueComparer)!; + } + + return (ValueComparer)Activator.CreateInstance( + typeof(ConvertingValueComparer<,>).MakeGenericType(targetClrType, valueComparer.Type), + valueComparer)!; + } } diff --git a/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs b/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs index d95a40877db..15f3e957fd1 100644 --- a/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs +++ b/src/EFCore/ChangeTracking/ListOfNullableValueTypesComparer.cs @@ -33,15 +33,15 @@ public sealed class ListOfNullableValueTypesComparer : private static readonly MethodInfo CompareMethod = typeof(ListOfNullableValueTypesComparer).GetMethod( nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(IEnumerable), typeof(ValueComparer)])!; + [typeof(IEnumerable), typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(ListOfNullableValueTypesComparer).GetMethod( nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(ValueComparer)])!; + [typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo SnapshotMethod = typeof(ListOfNullableValueTypesComparer).GetMethod( nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(ValueComparer)])!; + [typeof(IEnumerable), typeof(Func)])!; /// /// Creates a new instance of the list comparer. @@ -67,15 +67,13 @@ ValueComparer IInfrastructure.Instance var prm1 = Expression.Parameter(typeof(IEnumerable), "a"); var prm2 = Expression.Parameter(typeof(IEnumerable), "b"); - //(a, b) => Compare(a, b, (ValueComparer)elementComparer) + //(a, b) => Compare(a, b, elementComparer.Equals) return Expression.Lambda?, IEnumerable?, bool>>( Expression.Call( CompareMethod, prm1, prm2, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.EqualsExpression), prm1, prm2); } @@ -84,14 +82,12 @@ ValueComparer IInfrastructure.Instance { var prm = Expression.Parameter(typeof(IEnumerable), "o"); - //o => GetHashCode(o, (ValueComparer)elementComparer) + //o => GetHashCode(o, elementComparer.GetHashCode) return Expression.Lambda, int>>( Expression.Call( GetHashCodeMethod, prm, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.HashCodeExpression), prm); } @@ -99,18 +95,16 @@ ValueComparer IInfrastructure.Instance { var prm = Expression.Parameter(typeof(IEnumerable), "source"); - //source => Snapshot(source, (ValueComparer)elementComparer) + //source => Snapshot(source, elementComparer.Snapshot) return Expression.Lambda, IEnumerable>>( Expression.Call( SnapshotMethod, prm, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.SnapshotExpression), prm); } - private static bool Compare(IEnumerable? a, IEnumerable? b, ValueComparer elementComparer) + private static bool Compare(IEnumerable? a, IEnumerable? b, Func elementCompare) { if (ReferenceEquals(a, b)) { @@ -152,7 +146,7 @@ private static bool Compare(IEnumerable? a, IEnumerable? b return false; } - if (!elementComparer.Equals(el1, el2)) + if (!elementCompare(el1, el2)) { return false; } @@ -164,29 +158,29 @@ private static bool Compare(IEnumerable? a, IEnumerable? b throw new InvalidOperationException( CoreStrings.BadListType( (a is IList ? b : a).GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement).MakeNullable()).ShortDisplayName())); } - private static int GetHashCode(IEnumerable source, ValueComparer elementComparer) + private static int GetHashCode(IEnumerable source, Func elementGetHashCode) { var hash = new HashCode(); foreach (var el in source) { - hash.Add(el == null ? 0 : elementComparer.GetHashCode(el)); + hash.Add(el == null ? 0 : elementGetHashCode(el)); } return hash.ToHashCode(); } - private static IList Snapshot(IEnumerable source, ValueComparer elementComparer) + private static IList Snapshot(IEnumerable source, Func elementSnapshot) { if (source is not IList sourceList) { throw new InvalidOperationException( CoreStrings.BadListType( source.GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement).MakeNullable()).ShortDisplayName())); } if (IsArray) @@ -195,7 +189,7 @@ private static int GetHashCode(IEnumerable source, ValueComparer source, ValueComparer() : (IList)Activator.CreateInstance()!; foreach (var e in sourceList) { - snapshot.Add(e == null ? null : elementComparer.Snapshot(e)); + snapshot.Add(e == null ? null : elementSnapshot(e)); } return IsReadOnly diff --git a/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs b/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs index e0e77528d87..5bf444702eb 100644 --- a/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs +++ b/src/EFCore/ChangeTracking/ListOfReferenceTypesComparer.cs @@ -30,13 +30,13 @@ public sealed class ListOfReferenceTypesComparer : Valu && typeof(TConcreteList).GetGenericTypeDefinition() == typeof(ReadOnlyCollection<>)); private static readonly MethodInfo CompareMethod = typeof(ListOfReferenceTypesComparer).GetMethod( - nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(object), typeof(ValueComparer)])!; + nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(object), typeof(Func)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(ListOfReferenceTypesComparer).GetMethod( - nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo SnapshotMethod = typeof(ListOfReferenceTypesComparer).GetMethod( - nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(ValueComparer)])!; + nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(object), typeof(Func)])!; /// /// Creates a new instance of the list comparer. @@ -62,13 +62,12 @@ ValueComparer IInfrastructure.Instance var prm1 = Expression.Parameter(typeof(object), "a"); var prm2 = Expression.Parameter(typeof(object), "b"); - // (a, b) => Compare(a, b, elementComparer) return Expression.Lambda>( Expression.Call( CompareMethod, prm1, prm2, - elementComparer.ConstructorExpression), + elementComparer.EqualsExpression), prm1, prm2); } @@ -77,14 +76,13 @@ private static Expression> GetHashCodeLambda(ValueComparer ele { var prm = Expression.Parameter(typeof(object), "o"); - //o => GetHashCode((IEnumerable)o, elementComparer) return Expression.Lambda>( Expression.Call( GetHashCodeMethod, Expression.Convert( prm, typeof(IEnumerable)), - elementComparer.ConstructorExpression), + elementComparer.HashCodeExpression), prm); } @@ -92,16 +90,15 @@ private static Expression> SnapshotLambda(ValueComparer ele { var prm = Expression.Parameter(typeof(object), "source"); - //source => Snapshot(source, elementComparer) return Expression.Lambda>( Expression.Call( SnapshotMethod, prm, - elementComparer.ConstructorExpression), + elementComparer.SnapshotExpression), prm); } - private static bool Compare(object? a, object? b, ValueComparer elementComparer) + private static bool Compare(object? a, object? b, Func elementCompare) { if (ReferenceEquals(a, b)) { @@ -143,7 +140,7 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer) return false; } - if (!elementComparer.Equals(el1, el2)) + if (!elementCompare(el1, el2)) { return false; } @@ -155,29 +152,29 @@ private static bool Compare(object? a, object? b, ValueComparer elementComparer) throw new InvalidOperationException( CoreStrings.BadListType( (a is IList ? b : a).GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); } - private static int GetHashCode(IEnumerable source, ValueComparer elementComparer) + private static int GetHashCode(IEnumerable source, Func elementGetHashCode) { var hash = new HashCode(); foreach (var el in source) { - hash.Add(el == null ? 0 : elementComparer.GetHashCode(el)); + hash.Add(el == null ? 0 : elementGetHashCode((TElement?)el)); } return hash.ToHashCode(); } - private static IList Snapshot(object source, ValueComparer elementComparer) + private static IList Snapshot(object source, Func elementSnapshot) { if (source is not IList sourceList) { throw new InvalidOperationException( CoreStrings.BadListType( source.GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); } if (IsArray) @@ -186,7 +183,7 @@ private static int GetHashCode(IEnumerable source, ValueComparer elementComparer for (var i = 0; i < sourceList.Count; i++) { var instance = sourceList[i]; - snapshot[i] = instance == null ? null : (TElement?)elementComparer.Snapshot(instance); + snapshot[i] = instance == null ? null : elementSnapshot(instance); } return snapshot; @@ -196,7 +193,7 @@ private static int GetHashCode(IEnumerable source, ValueComparer elementComparer var snapshot = IsReadOnly ? new List() : (IList)Activator.CreateInstance()!; foreach (var e in sourceList) { - snapshot.Add(e == null ? null : (TElement?)elementComparer.Snapshot(e)); + snapshot.Add(e == null ? null : elementSnapshot(e)); } return IsReadOnly diff --git a/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs b/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs index 19a3a8d4a2f..6c6539b6c72 100644 --- a/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs +++ b/src/EFCore/ChangeTracking/ListOfValueTypesComparer.cs @@ -31,14 +31,14 @@ public sealed class ListOfValueTypesComparer : ValueCom private static readonly MethodInfo CompareMethod = typeof(ListOfValueTypesComparer).GetMethod( nameof(Compare), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(IEnumerable), typeof(ValueComparer)])!; + [typeof(IEnumerable), typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo GetHashCodeMethod = typeof(ListOfValueTypesComparer).GetMethod( nameof(GetHashCode), BindingFlags.Static | BindingFlags.NonPublic, - [typeof(IEnumerable), typeof(ValueComparer)])!; + [typeof(IEnumerable), typeof(Func)])!; private static readonly MethodInfo SnapshotMethod = typeof(ListOfValueTypesComparer).GetMethod( - nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(ValueComparer)])!; + nameof(Snapshot), BindingFlags.Static | BindingFlags.NonPublic, [typeof(IEnumerable), typeof(Func)])!; /// /// Creates a new instance of the list comparer. @@ -64,15 +64,13 @@ ValueComparer IInfrastructure.Instance var prm1 = Expression.Parameter(typeof(IEnumerable), "a"); var prm2 = Expression.Parameter(typeof(IEnumerable), "b"); - //(a, b) => Compare(a, b, (ValueComparer)elementComparer) + //(a, b) => Compare(a, b, elementComparer.Equals) return Expression.Lambda?, IEnumerable?, bool>>( Expression.Call( CompareMethod, prm1, prm2, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.EqualsExpression), prm1, prm2); } @@ -81,14 +79,12 @@ private static Expression, int>> GetHashCodeLambda(Va { var prm = Expression.Parameter(typeof(IEnumerable), "o"); - //o => GetHashCode(o, (ValueComparer)elementComparer) + //o => GetHashCode(o, elementComparer.GetHashCode) return Expression.Lambda, int>>( Expression.Call( GetHashCodeMethod, prm, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.HashCodeExpression), prm); } @@ -96,18 +92,16 @@ private static Expression, IEnumerable>> Sn { var prm = Expression.Parameter(typeof(IEnumerable), "source"); - //source => Snapshot(source, (ValueComparer)elementComparer) + //source => Snapshot(source, elementComparer.Snapshot) return Expression.Lambda, IEnumerable>>( Expression.Call( SnapshotMethod, prm, - Expression.Convert( - elementComparer.ConstructorExpression, - typeof(ValueComparer))), + elementComparer.SnapshotExpression), prm); } - private static bool Compare(IEnumerable? a, IEnumerable? b, ValueComparer elementComparer) + private static bool Compare(IEnumerable? a, IEnumerable? b, Func elementCompare) { if (ReferenceEquals(a, b)) { @@ -134,7 +128,7 @@ private static bool Compare(IEnumerable? a, IEnumerable? b, for (var i = 0; i < aList.Count; i++) { var (el1, el2) = (aList[i], bList[i]); - if (!elementComparer.Equals(el1, el2)) + if (!elementCompare(el1, el2)) { return false; } @@ -146,29 +140,29 @@ private static bool Compare(IEnumerable? a, IEnumerable? b, throw new InvalidOperationException( CoreStrings.BadListType( (a is IList ? b : a).GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement)).ShortDisplayName())); } - private static int GetHashCode(IEnumerable source, ValueComparer elementComparer) + private static int GetHashCode(IEnumerable source, Func elementGetHashCode) { var hash = new HashCode(); foreach (var el in source) { - hash.Add(elementComparer.GetHashCode(el)); + hash.Add(elementGetHashCode(el)); } return hash.ToHashCode(); } - private static IList Snapshot(IEnumerable source, ValueComparer elementComparer) + private static IList Snapshot(IEnumerable source, Func elementSnapshot) { if (source is not IList sourceList) { throw new InvalidOperationException( CoreStrings.BadListType( source.GetType().ShortDisplayName(), - typeof(IList<>).MakeGenericType(elementComparer.Type.MakeNullable()).ShortDisplayName())); + typeof(IList<>).MakeGenericType(typeof(TElement).MakeNullable()).ShortDisplayName())); } if (IsArray) @@ -177,7 +171,7 @@ private static IList Snapshot(IEnumerable source, ValueCompa for (var i = 0; i < sourceList.Count; i++) { var instance = sourceList[i]; - snapshot[i] = elementComparer.Snapshot(instance); + snapshot[i] = elementSnapshot(instance); } return snapshot; @@ -187,7 +181,7 @@ private static IList Snapshot(IEnumerable source, ValueCompa var snapshot = IsReadOnly ? new List() : (IList)Activator.CreateInstance()!; foreach (var e in sourceList) { - snapshot.Add(elementComparer.Snapshot(e)); + snapshot.Add(elementSnapshot(e)); } return IsReadOnly diff --git a/src/EFCore/Storage/TypeMappingSourceBase.cs b/src/EFCore/Storage/TypeMappingSourceBase.cs index b38c0e7462e..5474e119f7a 100644 --- a/src/EFCore/Storage/TypeMappingSourceBase.cs +++ b/src/EFCore/Storage/TypeMappingSourceBase.cs @@ -179,7 +179,7 @@ protected virtual bool TryFindJsonCollectionMapping( : elementType.IsValueType ? typeof(ListOfValueTypesComparer<,>).MakeGenericType(typeToInstantiate, elementType) : typeof(ListOfReferenceTypesComparer<,>).MakeGenericType(typeToInstantiate, elementType), - elementMapping.Comparer.ToNullableComparer(elementType)!); + elementMapping.Comparer.ComposeConversion(elementType)!); return true;