Added support for collections & dictionaries

- SortedCollectionEqualityComparer
- UnsortedCollectionEqualityComparer
- DictionaryEqualityComparer
This commit is contained in:
Carl de Billy 2018-02-13 12:09:15 -05:00
Родитель c677a034c1
Коммит 4d981ed50d
13 изменённых файлов: 1337 добавлений и 128 удалений

Просмотреть файл

@ -16,6 +16,7 @@
// ******************************************************************
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.DataAnnotations;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
@ -26,7 +27,7 @@ namespace Uno.CodeGen.Tests
public class Given_GeneratedEquality
{
[TestMethod]
public void EqualityWithCustomComparer()
public void Equality_WhenUsingCustomComparer()
{
var e1 = GeneratedImmutableEntityForEquality.Default.WithId("a");
var e2 = GeneratedImmutableEntityForEquality.Default.WithId("A");
@ -45,6 +46,137 @@ namespace Uno.CodeGen.Tests
typeof(GeneratedImmutableEntityForEquality).Should()
.HaveImplictConversionOperator<GeneratedImmutableEntityForEquality, GeneratedImmutableEntityForEquality.Builder>();
}
[TestMethod]
public void Equality_WhenUsingArray()
{
var e1 = new MyEntityForArrayAndDictionaryEquals.Builder {Array = new[] {"a", "b", "c"}}.ToImmutable();
var e2 = new MyEntityForArrayAndDictionaryEquals.Builder {Array = new[] {"a", "b", "c"}}.ToImmutable();
var e3 = new MyEntityForArrayAndDictionaryEquals.Builder {Array = new[] {"a", "b", "c", "d"}}.ToImmutable();
e1.Should().NotBeNull();
e2.Should().NotBeNull();
e3.Should().NotBeNull();
(e1 == e2).Should().BeTrue();
(e1 == e3).Should().BeFalse();
e1.Equals(e2).Should().BeTrue();
e1.Equals(e3).Should().BeFalse();
}
[TestMethod]
public void Equality_WhenUsingCollectionsAndDictionaries()
{
new MyEntityForAllCollectionsAndDictionaryTypes()
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes()).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(arraySorted: new[] {"a", "b"})
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(arraySorted: new[] {"a", "b"})).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(arraySorted: new[] { "a", "b" })
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(arraySorted: new[] { "b", "a" })).Should().BeFalse();
new MyEntityForAllCollectionsAndDictionaryTypes(arrayUnsorted: new[] { "a", "b" })
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(arrayUnsorted: new[] { "a", "b" })).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(arrayUnsorted: new[] { "a", "b" })
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(arrayUnsorted: new[] { "b", "a" })).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(listSorted: new List<string>{"a", "b"})
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(listSorted: new List<string> { "a", "b" })).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(listSorted: new List<string> { "a", "b" })
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(listSorted: new List<string> { "b", "a" })).Should().BeFalse();
new MyEntityForAllCollectionsAndDictionaryTypes(listUnsorted: new List<string> { "a", "b" })
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(listUnsorted: new List<string> { "a", "b" })).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(listUnsorted: new List<string> { "a", "b" })
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(listUnsorted: new List<string> { "b", "a" })).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(readonlyCollectionSorted: ImmutableList.Create("a", "b"))
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(readonlyCollectionSorted: ImmutableList.Create("a", "b"))).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(readonlyCollectionSorted: ImmutableList.Create("a", "b"))
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(readonlyCollectionSorted: ImmutableList.Create("b", "a"))).Should().BeFalse();
new MyEntityForAllCollectionsAndDictionaryTypes(readonlyCollectionUnsorted: ImmutableList.Create("a", "b"))
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(readonlyCollectionUnsorted: ImmutableList.Create("a", "b"))).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(readonlyCollectionUnsorted: ImmutableList.Create("a", "b"))
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(readonlyCollectionUnsorted: ImmutableList.Create("b", "a"))).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(dictionary: new Dictionary<string, string>{{"a", "a"}, {"b", "b"}})
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(dictionary: new Dictionary<string, string> { { "a", "a" }, { "b", "b" } })).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(dictionary: new Dictionary<string, string> { { "a", "a" }, { "b", "b" } })
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(dictionary: new Dictionary<string, string> { { "b", "b" }, { "a", "a" } })).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(dictionary: new Dictionary<string, string> { { "a", "a" }, { "b", "b" } })
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(dictionary: new Dictionary<string, string> { { "a", "a" }, { "b", "z" } })).Should().BeFalse();
new MyEntityForAllCollectionsAndDictionaryTypes(dictionary: new Dictionary<string, string> { { "a", "a" }, { "b", "b" } })
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(dictionary: new Dictionary<string, string> { { "a", "a" }, { "c", "c" } })).Should().BeFalse();
new MyEntityForAllCollectionsAndDictionaryTypes(readonlyDictionary: ImmutableDictionary<string, string>.Empty.Add("a", "a").Add("b", "b"))
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(readonlyDictionary: ImmutableDictionary<string, string>.Empty.Add("a", "a").Add("b", "b"))).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(readonlyDictionary: ImmutableDictionary<string, string>.Empty.Add("a", "a").Add("b", "b"))
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(readonlyDictionary: ImmutableDictionary<string, string>.Empty.Add("b", "b").Add("a", "a"))).Should().BeTrue();
new MyEntityForAllCollectionsAndDictionaryTypes(readonlyDictionary: ImmutableDictionary<string, string>.Empty.Add("a", "a").Add("b", "b"))
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(readonlyDictionary: ImmutableDictionary<string, string>.Empty.Add("a", "a").Add("b", "z"))).Should().BeFalse();
new MyEntityForAllCollectionsAndDictionaryTypes(readonlyDictionary: ImmutableDictionary<string, string>.Empty.Add("a", "a").Add("b", "b"))
.Equals(new MyEntityForAllCollectionsAndDictionaryTypes(readonlyDictionary: ImmutableDictionary<string, string>.Empty.Add("a", "a").Add("c", "c"))).Should().BeFalse();
}
[TestMethod]
public void Equality_WhenUsingMyEntityForStringComparison()
{
new MyEntityForStringComparison.Builder {DefaultMode = "a"}.ToImmutable()
.Equals(new MyEntityForStringComparison.Builder {DefaultMode = "a"}.ToImmutable()).Should().BeTrue();
new MyEntityForStringComparison.Builder { DefaultMode = "a" }.ToImmutable()
.Equals(new MyEntityForStringComparison.Builder { DefaultMode = "A" }.ToImmutable()).Should().BeFalse();
new MyEntityForStringComparison.Builder { DefaultMode = "" }.ToImmutable()
.Equals(MyEntityForStringComparison.Default).Should().BeFalse();
new MyEntityForStringComparison.Builder { IgnoreCase = "a" }.ToImmutable()
.Equals(new MyEntityForStringComparison.Builder { IgnoreCase = "a" }.ToImmutable()).Should().BeTrue();
new MyEntityForStringComparison.Builder { IgnoreCase = "a" }.ToImmutable()
.Equals(new MyEntityForStringComparison.Builder { IgnoreCase = "A" }.ToImmutable()).Should().BeTrue();
new MyEntityForStringComparison.Builder { IgnoreCase = "" }.ToImmutable()
.Equals(MyEntityForStringComparison.Default).Should().BeFalse();
new MyEntityForStringComparison.Builder { EmptyEqualsNull = "a" }.ToImmutable()
.Equals(new MyEntityForStringComparison.Builder { EmptyEqualsNull = "a" }.ToImmutable()).Should().BeTrue();
new MyEntityForStringComparison.Builder { EmptyEqualsNull = "a" }.ToImmutable()
.Equals(new MyEntityForStringComparison.Builder { EmptyEqualsNull = "A" }.ToImmutable()).Should().BeFalse();
new MyEntityForStringComparison.Builder { EmptyEqualsNull = "" }.ToImmutable()
.Equals(MyEntityForStringComparison.Default).Should().BeTrue();
new MyEntityForStringComparison.Builder {EmptyEqualsNull = ""}.ToImmutable()
.Should().BeSameAs(MyEntityForStringComparison.Default);
new MyEntityForStringComparison.Builder { EmptyEqualsNullIgnoreCase = "a" }.ToImmutable()
.Equals(new MyEntityForStringComparison.Builder { EmptyEqualsNullIgnoreCase = "a" }.ToImmutable()).Should().BeTrue();
new MyEntityForStringComparison.Builder { EmptyEqualsNullIgnoreCase = "a" }.ToImmutable()
.Equals(new MyEntityForStringComparison.Builder { EmptyEqualsNullIgnoreCase = "A" }.ToImmutable()).Should().BeTrue();
new MyEntityForStringComparison.Builder { EmptyEqualsNullIgnoreCase = "" }.ToImmutable()
.Equals(MyEntityForStringComparison.Default).Should().BeTrue();
new MyEntityForStringComparison.Builder { EmptyEqualsNullIgnoreCase = "" }.ToImmutable()
.Should().BeSameAs(MyEntityForStringComparison.Default);
}
}
[GeneratedEquality]
@ -104,4 +236,83 @@ namespace Uno.CodeGen.Tests
private static IEqualityComparer<string> Id_CustomComparer => StringComparer.OrdinalIgnoreCase;
}
[GeneratedImmutable(GenerateEquality = true)]
public partial class MyEntityForArrayAndDictionaryEquals
{
[EqualityComparerOptions(CollectionMode = CollectionComparerMode.Unsorted)]
public string[] Array { get; } = { };
public ImmutableDictionary<string, string> Dictionary { get; } = ImmutableDictionary<string, string>.Empty;
}
[GeneratedImmutable(GenerateEquality = true)]
public partial class MyEntityForStringComparison
{
[EqualityComparerOptions(StringMode = StringComparerMode.Default)]
public string DefaultMode { get; }
[EqualityComparerOptions(StringMode = StringComparerMode.IgnoreCase)]
public string IgnoreCase { get; }
[EqualityComparerOptions(StringMode = StringComparerMode.EmptyEqualsNull)]
public string EmptyEqualsNull { get; }
[EqualityComparerOptions(StringMode = StringComparerMode.EmptyEqualsNull | StringComparerMode.IgnoreCase)]
public string EmptyEqualsNullIgnoreCase { get; }
}
[GeneratedEquality]
public partial class MyEntityForAllCollectionsAndDictionaryTypes
{
public MyEntityForAllCollectionsAndDictionaryTypes(
string[] arraySorted = null,
string[] arrayUnsorted = null,
List<string> listSorted = null,
List<string> listUnsorted = null,
IImmutableList<string> readonlyCollectionSorted = null,
IImmutableList<string> readonlyCollectionUnsorted = null,
IImmutableDictionary<string, string> readonlyDictionary = null,
Dictionary<string, string> dictionary = null)
{
ArraySorted = arraySorted;
ArrayUnsorted = arrayUnsorted;
ListSorted = listSorted;
ListUnsorted = listUnsorted;
ReadonlyCollectionSorted = readonlyCollectionSorted;
ReadonlyCollectionUnsorted = readonlyCollectionUnsorted;
ReadonlyDictionary = readonlyDictionary;
Dictionary = dictionary;
}
[EqualityHash]
[EqualityComparerOptions(CollectionMode = CollectionComparerMode.Sorted)]
public string[] ArraySorted { get; }
[EqualityHash]
[EqualityComparerOptions(CollectionMode = CollectionComparerMode.Unsorted)]
public string[] ArrayUnsorted { get; }
[EqualityHash]
[EqualityComparerOptions(CollectionMode = CollectionComparerMode.Sorted)]
public List<string> ListSorted { get; }
[EqualityHash]
[EqualityComparerOptions(CollectionMode = CollectionComparerMode.Unsorted)]
public List<string> ListUnsorted { get; }
[EqualityHash]
[EqualityComparerOptions(CollectionMode = CollectionComparerMode.Sorted)]
public IImmutableList<string> ReadonlyCollectionSorted { get; }
[EqualityHash]
[EqualityComparerOptions(CollectionMode = CollectionComparerMode.Unsorted)]
public IImmutableList<string> ReadonlyCollectionUnsorted { get; }
[EqualityHash]
public IImmutableDictionary<string, string> ReadonlyDictionary { get; }
[EqualityHash]
public Dictionary<string, string> Dictionary { get; }
}
}

Просмотреть файл

@ -180,7 +180,7 @@ namespace Uno.CodeGen.Tests
{
var json = JsonConvert.SerializeObject(A.Default.WithEntity(x => null).ToImmutable());
json.Should().BeEquivalentTo("{\"T\":null,\"Entity\":null,\"IsSomething\":true}");
json.Should().BeEquivalentTo("{\"T\":null,\"Entity\":null,\"IsSomething\":true,\"Metadata\":null}");
}
[TestMethod]
@ -188,7 +188,7 @@ namespace Uno.CodeGen.Tests
{
var json = JsonConvert.SerializeObject(A.Default.WithEntity(x => null));
json.Should().BeEquivalentTo("{\"T\":null,\"Entity\":null,\"IsSomething\":true}");
json.Should().BeEquivalentTo("{\"T\":null,\"Entity\":null,\"IsSomething\":true,\"Metadata\":null}");
}
}

Просмотреть файл

@ -16,6 +16,7 @@
// ******************************************************************
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
@ -30,12 +31,20 @@ namespace Uno
[GenerateAfter("Uno.ImmutableGenerator")]
public class EqualityGenerator : SourceGenerator
{
private const int CollectionModeSorted = (int)CollectionComparerMode.Sorted; // this reference won't survive compilation
private const int CollectionModeUnsorted = (int)CollectionComparerMode.Sorted; // this reference won't survive compilation
private const byte StringModeIgnoreCase = (byte)StringComparerMode.IgnoreCase; // this reference won't survive compilation
private const byte StringModeEmptyEqualsNull = (byte)StringComparerMode.EmptyEqualsNull; // this reference won't survive compilation
private INamedTypeSymbol _objectSymbol;
private INamedTypeSymbol _valueTypeSymbol;
private INamedTypeSymbol _boolSymbol;
private INamedTypeSymbol _intSymbol;
private INamedTypeSymbol _stringSymbol;
private INamedTypeSymbol _arraySymbol;
private INamedTypeSymbol _collectionSymbol;
private INamedTypeSymbol _readonlyCollectionGenericSymbol;
private INamedTypeSymbol _collectionGenericSymbol;
private INamedTypeSymbol _iEquatableSymbol;
private INamedTypeSymbol _iKeyEquatableSymbol;
@ -44,6 +53,7 @@ namespace Uno
private INamedTypeSymbol _ignoreForEqualityAttributeSymbol;
private INamedTypeSymbol _equalityHashCodeAttributeSymbol;
private INamedTypeSymbol _equalityKeyCodeAttributeSymbol;
private INamedTypeSymbol _equalityComparerOptionsAttributeSymbol;
private INamedTypeSymbol _dataAnnonationsKeyAttributeSymbol;
private SourceGeneratorContext _context;
@ -82,8 +92,10 @@ namespace Uno
_valueTypeSymbol = context.Compilation.GetTypeByMetadataName("System.ValueType");
_boolSymbol = context.Compilation.GetTypeByMetadataName("System.Bool");
_intSymbol = context.Compilation.GetTypeByMetadataName("System.Int32");
_stringSymbol = context.Compilation.GetTypeByMetadataName("System.String");
_arraySymbol = context.Compilation.GetTypeByMetadataName("System.Array");
_collectionSymbol = context.Compilation.GetTypeByMetadataName("System.Collections.ICollection");
_readonlyCollectionGenericSymbol = context.Compilation.GetTypeByMetadataName("System.Collections.Generic.IReadOnlyCollection`1");
_collectionGenericSymbol = context.Compilation.GetTypeByMetadataName("System.Collections.Generic.ICollection`1");
_iEquatableSymbol = context.Compilation.GetTypeByMetadataName("System.IEquatable`1");
_iKeyEquatableSymbol = context.Compilation.GetTypeByMetadataName("Uno.Equality.IKeyEquatable");
@ -92,11 +104,11 @@ namespace Uno
_ignoreForEqualityAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.EqualityIgnoreAttribute");
_equalityHashCodeAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.EqualityHashAttribute");
_equalityKeyCodeAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.EqualityKeyAttribute");
_equalityComparerOptionsAttributeSymbol = context.Compilation.GetTypeByMetadataName("Uno.EqualityComparerOptionsAttribute");
_dataAnnonationsKeyAttributeSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.KeyAttribute");
_generateKeyEqualityCode = _iKeyEquatableSymbol != null;
foreach (var type in EnumerateEqualityTypesToGenerate())
{
GenerateEquality(type);
@ -163,7 +175,7 @@ namespace Uno
{
if (typeSymbol.IsReferenceType)
{
builder.AppendLineInvariant("if (ReferenceEquals(a, b)) return true; // Same instance");
builder.AppendLineInvariant("if (ReferenceEquals(a, b)) return true; // Same instance or both null");
using (builder.BlockInvariant("if (ReferenceEquals(null, a))"))
{
builder.AppendLineInvariant("return ReferenceEquals(null, b);");
@ -408,18 +420,95 @@ namespace Uno
if (customComparerProperty == null)
{
builder.AppendLineInvariant($"// **{member.Name}** You can define a custom comparer for {member.Name} and it will be used.");
builder.AppendLineInvariant($"// CUSTOM COMPARER>> private static IEqualityComparer<{type}> {GetCustomComparerPropertyName(member)} => <custom comparer>;");
using (builder.BlockInvariant(
$"if(!System.Collections.Generic.EqualityComparer<{type}>.Default.Equals({member.Name}, other.{member.Name}))"))
if (type.IsDictionary(out var keyType, out var valueType, out var isReadonlyDictionary))
{
builder.AppendLineInvariant($"return false; // {member.Name} not equal");
var comparer = isReadonlyDictionary
? "ReadonlyDictionaryEqualityComparer"
: "DictionaryEqualityComparer";
using (builder.BlockInvariant($"if(!global::Uno.Equality.{comparer}<{type}, {keyType}, {valueType}>.Default.Equals({member.Name}, other.{member.Name}))"))
{
builder.AppendLineInvariant($"return false; // {member.Name} not equal");
}
}
else if (type.IsCollection(out var elementType, out var isReadonlyCollection, out var isOrdered))
{
// Extract mode from attribute, or default following the type of the collection (if ordered)
var optionsAttribute = member.FindAttribute(_equalityComparerOptionsAttributeSymbol);
var mode = (int)(optionsAttribute
?.NamedArguments
.FirstOrDefault(na=>na.Key.Equals(nameof(EqualityComparerOptionsAttribute.CollectionMode)))
.Value.Value
?? (isOrdered ? CollectionModeSorted : CollectionModeUnsorted));
var comparer = (mode & CollectionModeSorted) == CollectionModeSorted
? (isReadonlyCollection ? "SortedReadonlyCollectionEqualityComparer" : "SortedCollectionEqualityComparer")
: (isReadonlyCollection ? "UnsortedReadonlyCollectionEqualityComparer" : "UnsortedCollectionEqualityComparer");
if (optionsAttribute == null && mode == CollectionModeSorted)
{
// We just show that if the collection is "sorted", because "unsorted" will always work and we don't want
// to do a "SequenceEquals" on an non-ordered structure.
builder.AppendLineInvariant($"// **{member.Name}** To use an _unsorted_ comparer, add the following attribute to your member:");
builder.AppendLineInvariant("// [EqualityCollection(CollectionComparerMode.Unsorted)]");
}
using (builder.BlockInvariant($"if(!global::Uno.Equality.{comparer}<{type}, {elementType}>.Default.Equals({member.Name}, other.{member.Name}))"))
{
builder.AppendLineInvariant($"return false; // {member.Name} not equal");
}
}
else
{
builder.AppendLineInvariant($"// **{member.Name}** You can define a custom comparer for {member.Name} and it will be used.");
builder.AppendLineInvariant($"// CUSTOM COMPARER>> private static IEqualityComparer<{type}> {GetCustomComparerPropertyName(member)} => <custom comparer>;");
if (type.Equals(_stringSymbol))
{
// Extract mode from attribute, or default following the type of the collection (if ordered)
var optionsAttribute = member.FindAttribute(_equalityComparerOptionsAttributeSymbol);
var mode = (byte) (optionsAttribute
?.NamedArguments
.FirstOrDefault(na => na.Key.Equals(nameof(EqualityComparerOptionsAttribute.StringMode)))
.Value.Value
?? default(byte));
var comparer = (mode & StringModeIgnoreCase) == StringModeIgnoreCase
? "System.StringComparer.OrdinalIgnoreCase"
: "System.StringComparer.Ordinal";
var emptyEqualsNull = (mode & StringModeEmptyEqualsNull) == StringModeEmptyEqualsNull;
var emptyCheck = emptyEqualsNull
? $"(string.IsNullOrWhiteSpace({member.Name}) != string.IsNullOrWhiteSpace(other.{member.Name})) || !string.IsNullOrWhiteSpace({member.Name}) && "
: "";
var nullCoalescing = emptyEqualsNull ? " ?? \"\" " : "";
using (builder.BlockInvariant($"if({emptyCheck}!{comparer}.Equals({member.Name}{nullCoalescing}, other.{member.Name}{nullCoalescing}))"))
{
builder.AppendLineInvariant($"return false; // {member.Name} not equal");
}
}
else if (type.IsPrimitive())
{
using (builder.BlockInvariant($"if({member.Name} != other.{member.Name})"))
{
builder.AppendLineInvariant($"return false; // {member.Name} not equal");
}
}
else
{
using (builder.BlockInvariant($"if(!System.Collections.Generic.EqualityComparer<{type}>.Default.Equals({member.Name}, other.{member.Name}))"))
{
builder.AppendLineInvariant($"return false; // {member.Name} not equal");
}
}
}
}
else
{
using (builder.BlockInvariant($"if(!{customComparerProperty.Name}.Equals({member.Name}, other.{member.Name})) // Using custom comparer provided by `{customComparerProperty.Name}()`."))
builder.AppendLineInvariant($"// **{member.Name}** using custom comparer provided by `{customComparerProperty.Name}()` {customComparerProperty.Locations.FirstOrDefault()}");
using (builder.BlockInvariant($"if(!{customComparerProperty.Name}.Equals({member.Name}, other.{member.Name}))"))
{
builder.AppendLineInvariant($"return false; // {member.Name} not equal");
}
@ -486,12 +575,7 @@ namespace Uno
builder.AppendLineInvariant($"// CUSTOM COMPARER>> private static IEqualityComparer<{type}> {GetCustomComparerPropertyName(member)} => <custom comparer>;");
}
var definition = type;
while (definition is INamedTypeSymbol nts && !nts.ConstructedFrom.Equals(definition))
{
definition = nts.ConstructedFrom;
}
var definition = type.GetDefinitionType();
string getHashCode;
if (customHashMethod != null)
@ -514,34 +598,45 @@ namespace Uno
{
getHashCode = $"{member.Name}.Length";
}
else if (type.IsDictionary(out var dictionaryKeyType, out var dictionaryValueType, out var isReadonlyDictionary))
{
getHashCode = isReadonlyDictionary
? $"((global::System.Collections.Generic.IReadOnlyDictionary<{dictionaryKeyType}, {dictionaryValueType}>){member.Name}).Count"
: $"((global::System.Collections.Generic.IDictionary<{dictionaryKeyType}, {dictionaryValueType}>){member.Name}).Count";
}
else if (type.IsCollection(out var collectionElementType, out var isReadonlyCollection, out var _))
{
getHashCode = isReadonlyCollection
? $"((global::System.Collections.Generic.IReadOnlyCollection<{collectionElementType}>){member.Name}).Count"
: $"((global::System.Collections.Generic.ICollection<{collectionElementType}>){member.Name}).Count";
}
else if (definition.Equals(_collectionSymbol)
|| definition.DerivesFromType(_collectionSymbol))
{
getHashCode = $"((global::System.Collections.ICollection){member.Name}).Count";
}
else if (definition.Equals(_collectionGenericSymbol)
|| definition.DerivesFromType(_collectionGenericSymbol))
{
getHashCode = $"((global::System.Collections.Generic.ICollection<{type.GetTypeArgumentNames().FirstOrDefault()}>){member.Name}).Count";
}
else
{
var getHashCodeMember = definition
.GetMembers("GetHashCode")
.OfType<IMethodSymbol>()
.Where(m => !m.IsStatic)
.Where(m => m.IsOverride)
.Where(m => !m.IsAbstract)
.FirstOrDefault();
if (getHashCodeMember == null)
var isGeneratedEquality = type.FindAttribute(_generatedEqualityAttributeSymbol) != null;
if (!isGeneratedEquality)
{
builder.AppendLineInvariant(
$"#warning Type `{type.GetDisplayFriendlyName()}` of member `{member.Name}` " +
"doesn't implements .GetHashCode(): it won't be used for hash computation. " +
$"If you can change the type {type}, you should use a custom hash method or " +
"a custom comparer.");
continue;
var getHashCodeMember = definition
.GetMembers("GetHashCode")
.OfType<IMethodSymbol>()
.Where(m => !m.IsStatic)
.Where(m => m.IsOverride)
.Where(m => !m.IsAbstract)
.FirstOrDefault();
if (getHashCodeMember == null)
{
builder.AppendLineInvariant(
$"#warning Type `{type.GetDisplayFriendlyName()}` of member `{member.Name}` " +
"doesn't implements .GetHashCode(): it won't be used for hash computation. " +
$"If you can't change the type {type}, you should use a custom hash method or " +
"a custom comparer. Alternatively, use something else for hash computation.");
continue;
}
}
getHashCode = $"{member.Name}.GetHashCode()";

Просмотреть файл

@ -14,6 +14,8 @@
// limitations under the License.
//
// ******************************************************************
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis;
@ -27,18 +29,8 @@ namespace Uno.Helpers
return type.GetType().Name.Equals("SourceTypeParameterSymbol");
}
public static bool IsImmutable(this ITypeSymbol type, bool treatArrayAsImmutable)
public static bool IsPrimitive(this ITypeSymbol type)
{
foreach (var attribute in type.GetAttributes())
{
switch (attribute.AttributeClass.ToString())
{
case "Uno.ImmutableAttribute":
case "Uno.GeneratedImmutableAttribute":
return true;
}
}
switch (type.SpecialType)
{
case SpecialType.System_Boolean:
@ -60,88 +52,257 @@ namespace Uno.Helpers
case SpecialType.System_UInt64:
case SpecialType.System_UIntPtr:
return true;
case SpecialType.None:
case SpecialType.System_Collections_Generic_IReadOnlyCollection_T:
case SpecialType.System_Collections_Generic_IReadOnlyList_T:
break; // need further validation
default:
return false;
}
if (type is IArrayTypeSymbol arrayType)
{
return arrayType.ElementType?.IsImmutable(treatArrayAsImmutable) ?? false;
}
var definitionType = type;
while ((definitionType as INamedTypeSymbol)?.ConstructedFrom.Equals(definitionType) == false)
{
definitionType = ((INamedTypeSymbol)definitionType).ConstructedFrom;
}
switch (definitionType.ToString())
{
case "System.Attribute": // strange, but valid
case "System.DateTime":
case "System.DateTimeOffset":
case "System.TimeSpan":
case "System.Type":
case "System.Uri":
case "System.Version":
return true;
// .NET framework
case "System.Collections.Generic.IReadOnlyList<T>":
case "System.Collections.Generic.IReadOnlyCollection<T>":
case "System.Nullable<T>":
case "System.Tuple<T>":
// System.Collections.Immutable (nuget package)
case "System.Collections.Immutable.IImmutableList<T>":
case "System.Collections.Immutable.IImmutableQueue<T>":
case "System.Collections.Immutable.IImmutableSet<T>":
case "System.Collections.Immutable.IImmutableStack<T>":
case "System.Collections.Immutable.ImmutableArray<T>":
case "System.Collections.Immutable.ImmutableHashSet<T>":
case "System.Collections.Immutable.ImmutableList<T>":
case "System.Collections.Immutable.ImmutableQueue<T>":
case "System.Collections.Immutable.ImmutableSortedSet<T>":
case "System.Collections.Immutable.ImmutableStack<T>":
{
var argumentParameter = (type as INamedTypeSymbol)?.TypeArguments.FirstOrDefault();
return argumentParameter == null || argumentParameter.IsImmutable(treatArrayAsImmutable);
}
case "System.Collections.Immutable.IImmutableDictionary<TKey, TValue>":
case "System.Collections.Immutable.ImmutableDictionary<TKey, TValue>":
case "System.Collections.Immutable.ImmutableSortedDictionary<TKey, TValue>":
{
var keyTypeParameter = (type as INamedTypeSymbol)?.TypeArguments.FirstOrDefault();
var valueTypeParameter = (type as INamedTypeSymbol)?.TypeArguments.Skip(1).FirstOrDefault();
return (keyTypeParameter == null || keyTypeParameter.IsImmutable(treatArrayAsImmutable))
&& (valueTypeParameter == null || valueTypeParameter.IsImmutable(treatArrayAsImmutable));
}
}
switch (definitionType.GetType().Name)
{
case "TupleTypeSymbol":
return true; // tuples are immutables
}
switch (definitionType.BaseType?.ToString())
{
case "System.Enum":
return true;
case "System.Array":
return treatArrayAsImmutable;
}
if (definitionType.IsReferenceType)
{
return false;
}
return false;
}
private static ImmutableDictionary<(ITypeSymbol, bool), bool> _isImmutable =
ImmutableDictionary<(ITypeSymbol, bool), bool>.Empty;
public static bool IsImmutable(this ITypeSymbol type, bool treatArrayAsImmutable)
{
bool GetIsImmutable((ITypeSymbol type, bool treatArrayAsImmutable) x)
{
var (t, asImmutable) = x;
foreach (var attribute in t.GetAttributes())
{
switch (attribute.AttributeClass.ToString())
{
case "Uno.ImmutableAttribute":
case "Uno.GeneratedImmutableAttribute":
return true;
}
}
if (t.IsPrimitive())
{
return true;
}
switch (t.SpecialType)
{
case SpecialType.None:
case SpecialType.System_Collections_Generic_IReadOnlyCollection_T:
case SpecialType.System_Collections_Generic_IReadOnlyList_T:
break; // need further validation
default:
return false;
}
if (t is IArrayTypeSymbol arrayType)
{
return arrayType.ElementType?.IsImmutable(asImmutable) ?? false;
}
var definitionType = t.GetDefinitionType();
switch (definitionType.ToString())
{
case "System.Attribute": // strange, but valid
case "System.DateTime":
case "System.DateTimeOffset":
case "System.TimeSpan":
case "System.Type":
case "System.Uri":
case "System.Version":
return true;
// .NET framework
case "System.Collections.Generic.IReadOnlyList<T>":
case "System.Collections.Generic.IReadOnlyCollection<T>":
case "System.Nullable<T>":
case "System.Tuple<T>":
// System.Collections.Immutable (nuget package)
case "System.Collections.Immutable.IImmutableList<T>":
case "System.Collections.Immutable.IImmutableQueue<T>":
case "System.Collections.Immutable.IImmutableSet<T>":
case "System.Collections.Immutable.IImmutableStack<T>":
case "System.Collections.Immutable.ImmutableArray<T>":
case "System.Collections.Immutable.ImmutableHashSet<T>":
case "System.Collections.Immutable.ImmutableList<T>":
case "System.Collections.Immutable.ImmutableQueue<T>":
case "System.Collections.Immutable.ImmutableSortedSet<T>":
case "System.Collections.Immutable.ImmutableStack<T>":
{
var argumentParameter = (t as INamedTypeSymbol)?.TypeArguments.FirstOrDefault();
return argumentParameter == null || argumentParameter.IsImmutable(asImmutable);
}
case "System.Collections.Immutable.IImmutableDictionary<TKey, TValue>":
case "System.Collections.Immutable.ImmutableDictionary<TKey, TValue>":
case "System.Collections.Immutable.ImmutableSortedDictionary<TKey, TValue>":
{
var keyTypeParameter = (t as INamedTypeSymbol)?.TypeArguments.FirstOrDefault();
var valueTypeParameter = (t as INamedTypeSymbol)?.TypeArguments.Skip(1).FirstOrDefault();
return (keyTypeParameter == null || keyTypeParameter.IsImmutable(asImmutable))
&& (valueTypeParameter == null || valueTypeParameter.IsImmutable(asImmutable));
}
}
switch (definitionType.GetType().Name)
{
case "TupleTypeSymbol":
return true; // tuples are immutables
}
switch (definitionType.BaseType?.ToString())
{
case "System.Enum":
return true;
case "System.Array":
return asImmutable;
}
if (definitionType.IsReferenceType)
{
return false;
}
return false;
}
return ImmutableInterlocked.GetOrAdd(ref _isImmutable, (type, treatArrayAsImmutable), GetIsImmutable);
}
public static ITypeSymbol GetDefinitionType(this ITypeSymbol type)
{
var definitionType = type;
while ((definitionType as INamedTypeSymbol)?.ConstructedFrom.Equals(definitionType) == false)
{
definitionType = ((INamedTypeSymbol) definitionType).ConstructedFrom;
}
return definitionType;
}
private static ImmutableDictionary<ITypeSymbol, (ITypeSymbol, bool, bool)> _isCollection =
ImmutableDictionary<ITypeSymbol, (ITypeSymbol, bool, bool)>.Empty;
public static bool IsCollection(this ITypeSymbol type, out ITypeSymbol elementType, out bool isReadonlyCollection, out bool isOrdered)
{
(ITypeSymbol elementType, bool isReadonlyCollection, bool isOrdered) GetTypeArgumentIfItIsACollection(INamedTypeSymbol t)
{
if (t != null)
{
var interfaceDefinition = t.GetDefinitionType();
switch (interfaceDefinition.ToString())
{
case "System.Collections.Immutable.ImmutableHashSet<T>":
case "System.Collections.Immutable.IImmutableSet<T>":
{
return (t.TypeArguments[0], true, false);
}
case "System.Collections.Immutable.IImmutableList<T>":
case "System.Collections.Immutable.IImmutableQueue<T>":
case "System.Collections.Immutable.IImmutableStack<T>":
case "System.Collections.Immutable.ImmutableArray<T>":
case "System.Collections.Immutable.ImmutableList<T>":
case "System.Collections.Immutable.ImmutableQueue<T>":
case "System.Collections.Immutable.ImmutableSortedSet<T>":
case "System.Collections.Immutable.ImmutableStack<T>":
{
return (t.TypeArguments[0], true, true);
}
case "System.Collections.Generic.IReadOnlyCollection<T>":
{
return (t.TypeArguments[0], true, true);
}
case "System.Collections.Generic.HashSet<T>":
{
return (t.TypeArguments[0], false, false);
}
case "System.Collections.Generic.ICollection<T>":
case "System.Collections.Generic.List<T>":
case "System.Collections.Generic.LinkedList<T>":
case "System.Collections.Generic.Queue<T>":
case "System.Collections.Generic.SortedSet<T>":
case "System.Collections.Generic.Stack<T>":
{
return (t.TypeArguments[0], false, true);
}
}
}
return (default(ITypeSymbol), default(bool), default(bool));
}
(ITypeSymbol elementType, bool isReadonlyCollection, bool isOrdered) GetIsCollection(ITypeSymbol t)
{
var r = GetTypeArgumentIfItIsACollection(t as INamedTypeSymbol);
if (r.elementType != null)
{
return r;
}
foreach (var @interface in t.AllInterfaces)
{
r = GetTypeArgumentIfItIsACollection(@interface);
if (r.elementType != null)
{
return r;
}
}
return (default(ITypeSymbol), default(bool), default(bool));
}
(elementType, isReadonlyCollection, isOrdered) = ImmutableInterlocked.GetOrAdd(ref _isCollection, type, GetIsCollection);
return elementType != null;
}
private static ImmutableDictionary<ITypeSymbol, (ITypeSymbol, ITypeSymbol, bool)> _isDictionary =
ImmutableDictionary<ITypeSymbol, (ITypeSymbol, ITypeSymbol, bool)>.Empty;
public static bool IsDictionary(this ITypeSymbol type, out ITypeSymbol keyType, out ITypeSymbol valueType, out bool isReadonlyDictionary)
{
(ITypeSymbol keyType, ITypeSymbol valueType, bool isReadonlyDictionary) GetTypeArgumentIfItIsACollection(INamedTypeSymbol t)
{
if (t != null)
{
var interfaceDefinition = t.GetDefinitionType();
switch (interfaceDefinition.ToString())
{
case "System.Collections.Generic.IReadOnlyDictionary<TKey, TValue>":
{
return (t.TypeArguments[0], t.TypeArguments[1], true);
}
case "System.Collections.Generic.IDictionary<TKey, TValue>":
{
return (t.TypeArguments[0], t.TypeArguments[1], false);
}
}
}
return (null, null, false);
}
(ITypeSymbol keyType, ITypeSymbol valueType, bool isReadonlyDictionary) GetIsACollection(ITypeSymbol t)
{
var r = GetTypeArgumentIfItIsACollection(t as INamedTypeSymbol);
if (r.keyType != null)
{
return r;
}
foreach (var @interface in t.AllInterfaces)
{
r = GetTypeArgumentIfItIsACollection(@interface);
if (r.keyType != null)
{
return r;
}
}
return (null, null, false);
}
(keyType, valueType, isReadonlyDictionary) = ImmutableInterlocked.GetOrAdd(ref _isDictionary, type, GetIsACollection);
return keyType != null;
}
}
}

Просмотреть файл

@ -0,0 +1,36 @@
// ******************************************************************
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ******************************************************************
namespace Uno
{
/// <summary>
/// Use to qualify the collection mode in <see cref="EqualityComparerOptionsAttribute"/> attribute.
/// </summary>
public enum CollectionComparerMode : int
{
Default = 0,
/// <summary>
/// Use a comparer which allows for a different ordering between collections.
/// </summary>
Unsorted = 0b0000,
/// <summary>
/// Use a comparer which checks the ordering in the collections.
/// </summary>
Sorted = 0b0001,
}
}

Просмотреть файл

@ -0,0 +1,112 @@
// ******************************************************************
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ******************************************************************
using System;
using System.Collections.Generic;
namespace Uno.Equality
{
/// <summary>
/// <see cref="IEqualityComparer{T}"/> implementation for evaluating dictionaries
/// </summary>
public class DictionaryEqualityComparer<TDict, TKey, TValue> : IEqualityComparer<TDict>
where TDict : IDictionary<TKey, TValue>
{
private readonly bool _nullIsEmpty;
private readonly IEqualityComparer<TValue> _valueComparer;
public static IEqualityComparer<TDict> Default { get; } = new DictionaryEqualityComparer<TDict, TKey, TValue>();
/// <summary>
/// ctor
/// </summary>
/// <param name="valueComparer">Comparer to use to comparer an enumerated item</param>
/// <param name="nullIsEmpty">If null value should be compated as empty collection</param>
public DictionaryEqualityComparer(IEqualityComparer<TValue> valueComparer = null, bool nullIsEmpty = true)
{
_nullIsEmpty = nullIsEmpty;
_valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
}
/// <inheritdoc/>
public bool Equals(TDict x, TDict y)
{
return (Equals(x, y, _valueComparer, _nullIsEmpty));
}
/// <summary>
/// Static (instance-less) equality check
/// </summary>
public static bool Equals(TDict x, TDict y, IEqualityComparer<TValue> valueComparer, bool nullIsEmpty = true)
{
if (valueComparer == null)
{
throw new ArgumentNullException(nameof(valueComparer));
}
if (nullIsEmpty)
{
if (x == null)
{
return y == null || y.Count == 0;
}
if (y == null)
{
return x.Count == 0;
}
}
else
{
if (x == null)
{
return y == null;
}
if (y == null)
{
return false;
}
}
if (x.Count != y.Count)
{
return false;
}
foreach (var keyValue in x)
{
if (!y.TryGetValue(keyValue.Key, out var yValue))
{
return false; // key not found: dictionaries are not equal.
}
if (!valueComparer.Equals(keyValue.Value, yValue))
{
return false; // value for this key is different.
}
}
return true; // no difference found
}
/// <inheritdoc/>
public int GetHashCode(TDict obj)
{
return obj?.Count ?? 0;
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
// ******************************************************************
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ******************************************************************
using System;
namespace Uno
{
/// <summary>
/// Attribute to put on a collection field/property to specify the kind
/// of collection comparer to use.
/// </summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public class EqualityComparerOptionsAttribute : Attribute
{
public CollectionComparerMode CollectionMode { get; set; } = CollectionComparerMode.Default;
public StringComparerMode StringMode { get; set; } = StringComparerMode.Default;
}
}

Просмотреть файл

@ -0,0 +1,112 @@
// ******************************************************************
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ******************************************************************
using System;
using System.Collections.Generic;
namespace Uno.Equality
{
/// <summary>
/// <see cref="IEqualityComparer{T}"/> implementation for evaluating dictionaries
/// </summary>
public class ReadonlyDictionaryEqualityComparer<TDict, TKey, TValue> : IEqualityComparer<TDict>
where TDict : IReadOnlyDictionary<TKey, TValue>
{
private readonly bool _nullIsEmpty;
private readonly IEqualityComparer<TValue> _valueComparer;
public static IEqualityComparer<TDict> Default { get; } = new ReadonlyDictionaryEqualityComparer<TDict, TKey, TValue>();
/// <summary>
/// ctor
/// </summary>
/// <param name="valueComparer">Comparer to use to comparer an enumerated item</param>
/// <param name="nullIsEmpty">If null value should be compated as empty collection</param>
public ReadonlyDictionaryEqualityComparer(IEqualityComparer<TValue> valueComparer = null, bool nullIsEmpty = true)
{
_nullIsEmpty = nullIsEmpty;
_valueComparer = valueComparer ?? EqualityComparer<TValue>.Default;
}
/// <inheritdoc/>
public bool Equals(TDict x, TDict y)
{
return (Equals(x, y, _valueComparer, _nullIsEmpty));
}
/// <summary>
/// Static (instance-less) equality check
/// </summary>
public static bool Equals(TDict x, TDict y, IEqualityComparer<TValue> valueComparer, bool nullIsEmpty = true)
{
if (valueComparer == null)
{
throw new ArgumentNullException(nameof(valueComparer));
}
if (nullIsEmpty)
{
if (x == null)
{
return y == null || y.Count == 0;
}
if (y == null)
{
return x.Count == 0;
}
}
else
{
if (x == null)
{
return y == null;
}
if (y == null)
{
return false;
}
}
if (x.Count != y.Count)
{
return false;
}
foreach (var keyValue in x)
{
if (!y.TryGetValue(keyValue.Key, out var yValue))
{
return false; // key not found: dictionaries are not equal.
}
if (!valueComparer.Equals(keyValue.Value, yValue))
{
return false; // value for this key is different.
}
}
return true; // no difference found
}
/// <inheritdoc/>
public int GetHashCode(TDict obj)
{
return obj?.Count ?? 0;
}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// ******************************************************************
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ******************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
namespace Uno.Equality
{
/// <summary>
/// <see cref="IEqualityComparer{T}"/> implementation for evaluating sorted collections
/// </summary>
public class SortedCollectionEqualityComparer<TCollection, T> : IEqualityComparer<TCollection>
where TCollection : ICollection<T>
{
private readonly IEqualityComparer<T> _itemComparer;
private readonly bool _nullIsEmpty;
public static IEqualityComparer<TCollection> Default { get; } = new SortedCollectionEqualityComparer<TCollection, T>();
/// <summary>
/// ctor
/// </summary>
/// <param name="itemComparer">Comparer to use to comparer an enumerated item</param>
/// <param name="nullIsEmpty">If null value should be compated as empty collection</param>
public SortedCollectionEqualityComparer(IEqualityComparer<T> itemComparer = null, bool nullIsEmpty = true)
{
_itemComparer = itemComparer ?? EqualityComparer<T>.Default;
_nullIsEmpty = nullIsEmpty;
}
/// <inheritdoc/>
public bool Equals(TCollection x, TCollection y)
{
return(Equals(x, y, _itemComparer, _nullIsEmpty));
}
/// <summary>
/// Static (instance-less) equality check
/// </summary>
public static bool Equals(TCollection x, TCollection y, IEqualityComparer<T> itemComparer, bool nullIsEmpty = true)
{
if (itemComparer == null)
{
throw new ArgumentNullException(nameof(itemComparer));
}
if (nullIsEmpty)
{
if (x == null)
{
return y == null || y.Count == 0;
}
if (y == null)
{
return x.Count == 0;
}
}
else
{
if (x == null)
{
return y == null;
}
if (y == null)
{
return false;
}
}
if (x.Count != y.Count)
{
return false;
}
var sequenceEqual = x.SequenceEqual(y, itemComparer);
return sequenceEqual;
}
/// <inheritdoc/>
public int GetHashCode(TCollection obj)
{
return obj?.Count ?? 0;
}
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// ******************************************************************
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ******************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
namespace Uno.Equality
{
/// <summary>
/// <see cref="IEqualityComparer{T}"/> implementation for evaluating sorted collections
/// </summary>
public class SortedReadonlyCollectionEqualityComparer<TCollection, T> : IEqualityComparer<TCollection>
where TCollection : IReadOnlyCollection<T>
{
private readonly IEqualityComparer<T> _itemComparer;
private readonly bool _nullIsEmpty;
public static IEqualityComparer<TCollection> Default { get; } = new SortedReadonlyCollectionEqualityComparer<TCollection, T>();
/// <summary>
/// ctor
/// </summary>
/// <param name="itemComparer">Comparer to use to comparer an enumerated item</param>
/// <param name="nullIsEmpty">If null value should be compated as empty collection</param>
public SortedReadonlyCollectionEqualityComparer(IEqualityComparer<T> itemComparer = null, bool nullIsEmpty = true)
{
_itemComparer = itemComparer ?? EqualityComparer<T>.Default;
_nullIsEmpty = nullIsEmpty;
}
/// <inheritdoc/>
public bool Equals(TCollection x, TCollection y)
{
return (Equals(x, y, _itemComparer, _nullIsEmpty));
}
/// <summary>
/// Static (instance-less) equality check
/// </summary>
public static bool Equals(TCollection x, TCollection y, IEqualityComparer<T> itemComparer, bool nullIsEmpty = true)
{
if (itemComparer == null)
{
throw new ArgumentNullException(nameof(itemComparer));
}
if (nullIsEmpty)
{
if (x == null)
{
return y == null || y.Count == 0;
}
if (y == null)
{
return x.Count == 0;
}
}
else
{
if (x == null)
{
return y == null;
}
if (y == null)
{
return false;
}
}
if (x.Count != y.Count)
{
return false;
}
var sequenceEqual = x.SequenceEqual(y, itemComparer);
return sequenceEqual;
}
/// <inheritdoc/>
public int GetHashCode(TCollection obj)
{
return obj?.Count ?? 0;
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// ******************************************************************
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ******************************************************************
using System;
namespace Uno
{
/// <summary>
/// Use to qualify the string mode in <see cref="EqualityComparerOptionsAttribute"/> attribute.
/// </summary>
[Flags]
public enum StringComparerMode : byte
{
/// <summary>
/// No modes activated
/// </summary>
Default = 0,
/// <summary>
/// Ignore casing when comparing
/// </summary>
IgnoreCase = 0b0001,
/// <summary>
/// Treat empty/white strings and null as equal
/// </summary>
EmptyEqualsNull = 0b0010,
}
}

Просмотреть файл

@ -0,0 +1,103 @@
// ******************************************************************
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ******************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
namespace Uno.Equality
{
/// <summary>
/// <see cref="IEqualityComparer{T}"/> implementation for evaluating unsorted collections
/// </summary>
/// <remarks>
/// Will produce weird result on collection with repeated items (should be a "Set")
/// </remarks>
public class UnsortedCollectionEqualityComparer<TCollection, T> : IEqualityComparer<TCollection>
where TCollection : ICollection<T>
{
private readonly IEqualityComparer<T> _itemComparer;
private readonly bool _nullIsEmpty;
public static IEqualityComparer<TCollection> Default { get; } = new UnsortedCollectionEqualityComparer<TCollection, T>();
/// <summary>
/// ctor
/// </summary>
/// <param name="itemComparer">Comparer to use to comparer an enumerated item</param>
/// <param name="nullIsEmpty">If null value should be compated as empty collection</param>
public UnsortedCollectionEqualityComparer(IEqualityComparer<T> itemComparer = null, bool nullIsEmpty = true)
{
_itemComparer = itemComparer ?? EqualityComparer<T>.Default;
_nullIsEmpty = nullIsEmpty;
}
/// <inheritdoc/>
public bool Equals(TCollection x, TCollection y)
{
return (Equals(x, y, _itemComparer, _nullIsEmpty));
}
/// <summary>
/// Static (instance-less) equality check
/// </summary>
public static bool Equals(TCollection x, TCollection y, IEqualityComparer<T> itemComparer, bool nullIsEmpty = true)
{
if (itemComparer == null)
{
throw new ArgumentNullException(nameof(itemComparer));
}
if (nullIsEmpty)
{
if (x == null)
{
return y == null || y.Count == 0;
}
if (y == null)
{
return x.Count == 0;
}
}
else
{
if (x == null)
{
return y == null;
}
if (y == null)
{
return false;
}
}
if (x.Count != y.Count)
{
return false;
}
return x.All(item => y.Contains(item, itemComparer));
}
/// <inheritdoc/>
public int GetHashCode(TCollection obj)
{
return obj?.Count ?? 0;
}
}
}

Просмотреть файл

@ -0,0 +1,103 @@
// ******************************************************************
// Copyright <20> 2015-2018 nventive inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ******************************************************************
using System;
using System.Collections.Generic;
using System.Linq;
namespace Uno.Equality
{
/// <summary>
/// <see cref="IEqualityComparer{T}"/> implementation for evaluating unsorted collections
/// </summary>
/// <remarks>
/// Will produce weird result on collection with repeated items (should be a "Set")
/// </remarks>
public class UnsortedReadonlyCollectionEqualityComparer<TCollection, T> : IEqualityComparer<TCollection>
where TCollection : IReadOnlyCollection<T>
{
private readonly IEqualityComparer<T> _itemComparer;
private readonly bool _nullIsEmpty;
public static IEqualityComparer<TCollection> Default { get; } = new UnsortedReadonlyCollectionEqualityComparer<TCollection, T>();
/// <summary>
/// ctor
/// </summary>
/// <param name="itemComparer">Comparer to use to comparer an enumerated item</param>
/// <param name="nullIsEmpty">If null value should be compated as empty collection</param>
public UnsortedReadonlyCollectionEqualityComparer(IEqualityComparer<T> itemComparer = null, bool nullIsEmpty = true)
{
_itemComparer = itemComparer ?? EqualityComparer<T>.Default;
_nullIsEmpty = nullIsEmpty;
}
/// <inheritdoc/>
public bool Equals(TCollection x, TCollection y)
{
return (Equals(x, y, _itemComparer, _nullIsEmpty));
}
/// <summary>
/// Static (instance-less) equality check
/// </summary>
public static bool Equals(TCollection x, TCollection y, IEqualityComparer<T> itemComparer, bool nullIsEmpty = true)
{
if (itemComparer == null)
{
throw new ArgumentNullException(nameof(itemComparer));
}
if (nullIsEmpty)
{
if (x == null)
{
return y == null || y.Count == 0;
}
if (y == null)
{
return x.Count == 0;
}
}
else
{
if (x == null)
{
return y == null;
}
if (y == null)
{
return false;
}
}
if (x.Count != y.Count)
{
return false;
}
return x.All(item => y.Contains(item, itemComparer));
}
/// <inheritdoc/>
public int GetHashCode(TCollection obj)
{
return obj?.Count ?? 0;
}
}
}