Ответ 1
Вот то, что я собрал, основываясь на моем ограниченном опыте:
Однако результирующий тип несовместим с
IEquatable<MyType>
.
Это неверно, результирующий тип реализует IEquatable<MyType>
. Вы можете проверить в ILDasm. Пример:
[<StructuralEquality;StructuralComparison>]
type SomeType = {
Value : int
}
let someTypeAsIEquatable = { Value = 3 } :> System.IEquatable<SomeType>
someTypeAsIEquatable.Equals({Value = 3}) |> ignore // calls Equals(SomeType) directly
Возможно, вас смущает то, как F # не делает неявные восходящие потоки, такие как С#, поэтому, если вам нужно просто:
{ Value = 3 }.Equals({Value = 4})
Это фактически вызовет Equals (obj) вместо члена интерфейса, что противоречит ожиданиям, исходящим из С#.
Мне интересно, в каких случаях тесты равенства в F # вызывают бокс
Один общий и неприятный случай для любой структуры, определенной, например, С# и реализации IEquatable<T>
, например:
public struct Vector2f : IEquatable<Vector2f>
или аналогично, любая структура, определенная в F # с пользовательской реализацией IEquatable<T>
, например:
[<Struct;CustomEquality;NoComparison>]
type MyVal =
val X : int
new(x) = { X = x }
override this.Equals(yobj) =
match yobj with
| :? MyVal as y -> y.X = this.X
| _ -> false
interface System.IEquatable<MyVal> with
member this.Equals(other) =
other.X = this.X
Сравнение двух экземпляров этой структуры с оператором =
фактически вызывает Equals(obj)
вместо Equals(MyVal)
, что приводит к возникновению бокса в , причем оба значения сравниваются (а затем литье и распаковка). Примечание. Я сообщил об этом как об ошибке в Visualfsharp Github, и, очевидно, этот случай должен быть исправлен раньше, чем позже.
И если вы считаете, что приведение к IEquatable<T>
явно поможет, ну это будет, но это операция по боксу сама по себе. Но по крайней мере вы можете спасти себе один из двух боксов таким образом.
Я в замешательстве. Что происходит? Какое бы правильное равенство реализация выглядит так, если тип может использоваться как коллекция ключа или в частых тестах на равенство?
Я так же смущен, как и ты. F # кажется очень GC-happy (неважно, что это GCs Tuples и не поддерживает записи структуры или DU). Даже поведение по умолчанию:
[<Struct>]
type MyVal =
val X : int
new(x) = { X = x }
for i in 0 .. 1000000 do
(MyVal(i) = MyVal(i + 1)) |> ignore;;
Réel : 00:00:00.008, Processeur : 00:00:00.015, GC gén0: 4, gén1: 1, gén2: 0
Все еще вызывает бокс и чрезмерное давление в ГК! Ниже приведено обходное решение.
Что делать, если тип должен использоваться в качестве ключа, например. словарь? Ну, если он System.Collections.Generics.Dictionary
, вы в порядке, это не использует оператор равенства F #. Но любая коллекция, определенная в F #, которая использует этот оператор, будет явно сталкиваться с проблемами бокса.
Мне интересно (...) есть ли случаи, когда переопределение равен и GetHashCode и реализация IEquatable < > предпочтительнее использовать атрибут StructuralEqualityAttribute.
Суть заключается в том, чтобы определить ваше собственное пользовательское равенство, и в этом случае вы используете CustomEqualityAttribute
вместо StructuralEqualityAttribute
.
Если да, можно ли это сделать, не уменьшая производительность оператора =?
Обновление: Я предлагаю избегать использования по умолчанию (=) и напрямую использовать IEquatable (T).Equals. Вы можете определить для этого встроенный оператор, или вы можете даже переопределить (=) в терминах этого. Это делает практически все типы в F #, а для остальных он не будет компилироваться, поэтому вы не столкнетесь с небольшими ошибками. Я подробно описываю этот подход здесь.
Оригинал: Начиная с F # 4.0, вы можете сделать следующее (спасибо latkin):
[<Struct>]
type MyVal =
val X : int
new(x) = { X = x }
static member op_Equality(this : MyVal, other : MyVal) =
this.X = other.X
module NonStructural =
open NonStructuralComparison
let test () =
for i in 0 .. 10000000 do
(MyVal(i) = MyVal(i + 1)) |> ignore
// Real: 00:00:00.003, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0
NonStructural.test()
Модуль NonStructuralComparison переопределяет значение по умолчанию =
с версией, которая просто вызывает op_Equality
. Я бы добавил атрибуты NoEquality
и NoComparison
в структуру, чтобы убедиться, что вы случайно не используете невысокую производительность по умолчанию =
.