В record нельзя добавить собственную реализацию эквивалентности (переопределит Equals) и при это они не умеют реализовывать эквивалентность для записей со списками.
record R(int X, ImmutableArray<int> Xs);
static class P
{
static void Main()
{
var r1 = new R(1,ImmutableArray.Create(2));
var r2 = new R(1,ImmutableArray.Create(2));
Console.WriteLine(r1 == r2);
}
}
Выводит False!
С массивами тоже самое. Т.е. любая ссылка в параметре и прощай эквивалентность. Причем и вручную это не ясно как исправить.
А как жить то с такими говнорекордами? Вот на фиг так делать?
Я что-то пропустил? Как обойти это безобразие?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
VD>Я что-то пропустил? Как обойти это безобразие?
Там же ещё есть record struct, которые вообще ValueType. Поэтому переопределение в рекордах немножко своё. Нужно сделать Equals от IEquatable, но без override, компилятор поймёт:
public sealed record R(int x, int[] xs)
{
public bool Equals(R? other)
=> other.x == x && other.xs.SequenceEqual(xs);
}
Здравствуйте, hi_octane, Вы писали:
_>Там же ещё есть record struct, которые вообще ValueType.
Как-то раньше в структурах удавалось Equals переопределять ведь.
_>Поэтому переопределение в рекордах немножко своё. Нужно сделать Equals от IEquatable, но без override, компилятор поймёт:
_>
_>public sealed record R(int x, int[] xs)
_>{
_> public bool Equals(R? other)
_> => other.x == x && other.xs.SequenceEqual(xs);
_>}
_>
_>И тогда на == будет True
Что-то не очень понял. Правда вот так понял:
record R(int X, int[] Xs)
{
publicvirtualbool Equals(R? other) => other != null && other.X == X && other.Xs.SequenceEqual(Xs);
public override int GetHashCode()
{
return X;
}
}
Но в любом случае все это выглядит странным. А что что не реализована эквивалентность для вложенных последовательностей — это вообще лажа какая-то.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, hi_octane, Вы писали:
_>Там же ещё есть record struct, которые вообще ValueType.
Как-то раньше в структурах удавалось Equals переопределять ведь.
_>Поэтому переопределение в рекордах немножко своё. Нужно сделать Equals от IEquatable, но без override, компилятор поймёт:
_>
_>public sealed record R(int x, int[] xs)
_>{
_> public bool Equals(R? other)
_> => other.x == x && other.xs.SequenceEqual(xs);
_>}
_>
_>И тогда на == будет True
Что-то не очень понял. Правда вот так понял:
record R(int X, int[] Xs)
{
publicvirtualbool Equals(R? other) => other != null && other.X == X && other.Xs.SequenceEqual(Xs);
public override int GetHashCode()
{
return X;
}
}
Но в любом случае все это выглядит странным. А что что не реализована эквивалентность для вложенных последовательностей — это вообще лажа какая-то.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
VD>record R(int X, int[] Xs)
VD>{
VD> publicvirtualbool Equals(R? other) => other != null && other.X == X && other.Xs.SequenceEqual(Xs);
VD> public override int GetHashCode()
VD> {
VD> return X;
VD> }
VD>}
VD>
VD>Но в любом случае все это выглядит странным. А что что не реализована эквивалентность для вложенных последовательностей — это вообще лажа какая-то.
Коллекции суть ссылочный типы, естественно, равенство для них — это ссылочное равенство. Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual.
Здравствуйте, VladD2, Вы писали:
VD>Что-то прямо сижу и тихо охрениваю. VD>В record нельзя добавить собственную реализацию эквивалентности (переопределит Equals) и при это они не умеют реализовывать эквивалентность для записей со списками.
using System;
var r1 = new R("R");
var r2 = new R("R");
Console.WriteLine($"Compare {nameof(r1)} and {nameof(r2)} before subscription: {r1 == r2}");
r1.Event += delegate { };
Console.WriteLine($"Compare {nameof(r1)} and {nameof(r2)} after subscription: {r1 == r2}");
record R(string Name) {
public event EventHandler? Event;
}
P.S. По-честному, если рекорд не `sealed`, хорощо б и `EqualityContract` сравнивать и учитывать в хеше:
record R(string Name) {
public event EventHandler? Event;
public virtual bool Equals(R? other) => other != null && other.EqualityContract == EqualityContract && other.Name == Name;
public override int GetHashCode() => (EqualityContract, Name).GetHashCode();
}
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, yenik, Вы писали:
VD>>Но в любом случае все это выглядит странным. А что что не реализована эквивалентность для вложенных последовательностей — это вообще лажа какая-то. Y>Коллекции суть ссылочный типы, естественно, равенство для них — это ссылочное равенство. Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual.
Это не так. Компилятор использует `EqualityComparer<>.Default.Equals(, )` для сравнения членов, поэтому если коллекция будет иметь переопределённый `Equals`, то будет использван он, а не "ссылочное равенство".
Help will always be given at Hogwarts to those who ask for it.
VD>>Но в любом случае все это выглядит странным. А что что не реализована эквивалентность для вложенных последовательностей — это вообще лажа какая-то.
Y>Коллекции суть ссылочный типы, естественно, равенство для них — это ссылочное равенство. Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual.
F# :
> let arr1 = [|1;2;3|];;
val arr1 : int [] = [|1; 2; 3|]
> let arr2 = [|1;2;3|];;
val arr2 : int [] = [|1; 2; 3|]
> arr1 = arr2;;
val it : bool = true
> type Cart = { Id : int; Items : array<int> };;
type Cart =
{Id: int;
Items: int array;}
> let c1 = { Id = 1; Items = arr1 };;
val c1 : Cart = {Id = 1;
Items = [|1; 2; 3|];}
> let c2 = { Id = 1; Items = arr2 };;
val c2 : Cart = {Id = 1;
Items = [|1; 2; 3|];}
> c1 = c2;;
val it : bool = true
_FR>если коллекция будет иметь переопределённый `Equals`
И я о том: Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual. А если переопределить Equals, тогда конечно любые фантазии.
Можно было бы ещё вспомнить string, но это особенный тип коллекции.
Хорошее замечание. И хорошо, что этого нет в C#. Представляю себе два списка по миллиону элементов, и вдруг a == b их перебирает. Нет, если нужно SequenceEqual, то и вызывать нужно SequenceEqual в явном виде.
Здравствуйте, yenik, Вы писали:
Р>>F# :
Y>Хорошее замечание. И хорошо, что этого нет в C#. Представляю себе два списка по миллиону элементов, и вдруг a == b их перебирает. Нет, если нужно SequenceEqual, то и вызывать нужно SequenceEqual в явном виде.
Нет, если нужно ReferenceEquals , то и вызывать нужно ReferenceEquals в явном виде.
Здравствуйте, yenik, Вы писали:
Y>Коллекции суть ссылочный типы, естественно, равенство для них — это ссылочное равенство.
Рекорды тоже ссылочный тип. Но эквивалентность для них сделана по значению. Следственно естественного тут ничего нет. Это твои стереотипы.
Тут на лицо непоследовательность. Для одних типов делаем, для других нет.
Взялись повторять функциональные языки, но на полпути забили на это дело. В Немерле есть встроенный список, который сам по себе является алгебраическим типом и для него эквивалентность работает как надо. Тут же ничего подобного не предусмотрели. Сам метод переопределения эквивалентности сделали мягко говоря странно.
Y>Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual.
Что тут странного? Странно, что коллекции не поддерживают Equals и IEquatable из коробки.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Y>>Странным было бы, если бы их равенство по дефолту делалось через SequenceEqual.
VD>Что тут странного? Странно, что коллекции не поддерживают Equals и IEquatable из коробки.
Различная эквивалентность нужна. Например сравнивать через HashSet, сортированные списки по определенным полям, ReferenceEquals итд.
Кроме того надо разруливать циклические ссылки итд. Слишком много параметров сравнения. Проще для списков свою реализацию сделать.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, VladD2, Вы писали:
_FR>>P.S. По-честному, если рекорд не `sealed`, хорощо б и `EqualityContract` сравнивать и учитывать в хеше: VD>А кто-нибудь может в двух словах объяснить на фиг он вообще EqualityContract придумали?
Чтобы экземпляр наследника вдруг не смог бы быть равен экземпляру базового типа.
The EqualityContract enables the equality methods to compare the runtime type of objects when they're checking for equality.
using System;
var r = new R("A");
var x = new X("A");
Console.WriteLine($"Compare {nameof(r)} and {nameof(x)}: {r == x}"); // Falsevar r1 = new R1("A");
var x1 = new X1("A");
Console.WriteLine($"Compare {nameof(r1)} and {nameof(x1)}: {r1 == x1}"); // True
record R(string Name) { }
record X(string Name) : R(Name) { }
record R1(string Name) {
public virtual bool Equals(R1? other) => other is not null && other.Name == Name;
public override int GetHashCode() => Name?.GetHashCode() ?? 0;
}
record X1(string Name) : R1(Name) { }
Help will always be given at Hogwarts to those who ask for it.
VD>Тут на лицо непоследовательность. Для одних типов делаем, для других нет. VD>Что тут странного? Странно, что коллекции не поддерживают Equals и IEquatable из коробки.
Для типов class два объекта равны, если они ссылаются на один и тот же объект в памяти.
Для типов struct два объекта равны, если хранят одинаковые значения и имеют один и тот же тип.
Для типов с модификатором record (record class, record struct и readonly record struct) два объекта равны, если они относятся к одному типу и хранят одинаковые значения.
Коллекции в .NET относятся к типу class и по определению не поддерживают переопределение Equals и IEquatable из коробки. Если бы к существующим коллекциям вдруг приделали переопределение Equals, то это было бы несовместимое изменение. Если бы новые коллекции стали делать с Equals на основе поэлементного сравнения, то это было бы непоследовательно.
Типы record — это новшество, у которого равенство отличается от традиционной трактовки равенства объектов в C# и других популярных языках программирования.
Y>Для типов class два объекта равны, если они ссылаются на один и тот же объект в памяти.
Y>Для типов struct два объекта равны, если хранят одинаковые значения и имеют один и тот же тип.
Y>Для типов с модификатором record (record class, record struct и readonly record struct) два объекта равны, если они относятся к одному типу и хранят одинаковые значения.
Y>Коллекции в .NET относятся к типу class и по определению не поддерживают переопределение Equals и IEquatable из коробки.
Это определение вводит очень странное отношение равеснства объектов, пользоваться которым не сильно удобно, т.к. оно не указывает на то, могут ли быть равны две строки с одинаковыми значениями, но в разных участках памяти. То есть, даже для того, что бы понять как ведут себя рекорды со строковыми полями, мы не можем это определение использовать.
Кроме того, оно определяет равенство объектов через ссылку на объект, но ссылка не является объектом в системе типов .NET.
Y>Если бы к существующим коллекциям вдруг приделали переопределение Equals, то это было бы несовместимое изменение. Если бы новые коллекции стали делать с Equals на основе поэлементного сравнения, то это было бы непоследовательно. Y>Типы record — это новшество, у которого равенство отличается от традиционной трактовки равенства объектов в C# и других популярных языках программирования.
Традиционная трактовка через популярные языки — вообще что-то некодифицируемое. Мы в одном-то языке не можем разобраться.
Есть отношения эквивалентности и их реализации, которые могут быть хорошими или не очень. Пожалуй, это все, на что можно опираться. У рекордов — эквивалентность такая, какую сегодня дает компилятор, согласно спецификации рекордов (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/records). Разумеется, всем не угодишь, поэтому, она такая, какая есть, а в хелпах сова на глобусе.
Здравствуйте, _FRED_, Вы писали:
_FR>Чтобы экземпляр наследника вдруг не смог бы быть равен экземпляру базового типа.
А чем им для этого GetType() не подошел? Да и для структурной эквивалентности (что и должна обеспечиваться для записей) не нужно совпадение типов. Достаточно совпадения полей и их значений. Так что это должно быть как опция.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Serginio1, Вы писали:
S>Различная эквивалентность нужна. Например сравнивать через HashSet, сортированные списки по определенным полям, ReferenceEquals итд. Кроме того надо разруливать циклические ссылки итд. Слишком много параметров сравнения.
Тут есть два нюанса.
1. Удобство. Заставлять делать людей рутинную работу — глупо. Несомненно, если нужны какие-то нюансы, надо позволить людям заменить стандартную реализацию. Но по умолчанию должен работать самый ожидаемый вариант. Тогда не придется тратить время на непроизводительный труд.
2. Под записями (рекордами) лежат математические модели алгебраических типов и структурной типизации. Они не из пальца высосаны. И раз так, то нужно выдерживать все каноны этих моделей.
S>Проще для списков свою реализацию сделать.
Проще не делать ничего без необходимости. Проще, как раз, было бы реализовать для списков сравнение. И уже у конкретных видов коллекций сделать тот или ной вид сравнения. Для списков — последовательное сравнение (совпадение всех элементов), для сетов — совпадение множеств.
Ну и логично было бы сделать нормальный однонаправленный связанный список на рекордах, чтобы он был алгебраическим типом (как в Немерле).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
S>>Проще для списков свою реализацию сделать.
VD>Проще не делать ничего без необходимости. Проще, как раз, было бы реализовать для списков сравнение. И уже у конкретных видов коллекций сделать тот или ной вид сравнения. Для списков — последовательное сравнение (совпадение всех элементов), для сетов — совпадение множеств.
VD>Ну и логично было бы сделать нормальный однонаправленный связанный список на рекордах, чтобы он был алгебраическим типом (как в Немерле).
Как раз в большинстве вариантов нужны свои сравнения. Опять же Хэш код вычислять нужно время. А нужно то всего на всего сравнить ссылки
Я вот не помню когда пользовался односвязными списками вообще, только для буферов (страниц)
А так тебе придется для строк свои реализации с IgnoreCase делать итд.
Для списка достаточно по дефолту ReferenceEquals
и солнце б утром не вставало, когда бы не было меня