C #, несколько перегрузок оператора == без неоднозначной проверки на null

Введение:
У меня есть несколько классов, которые выполняют одну и ту же работу, но с разными типами значений (например, векторы с плавающей запятой или целые числа).
Теперь я хочу иметь возможность проверять равенство , это равенство также должно работать между типами (например, vectorF == vectorI).
Кроме того, должна быть предусмотрена возможность выполнения нулевой проверки (vectorF == null).

Подход:
Мой подход заключается в создании нескольких перегрузок для операторов == и! =, по одной для каждой возможной комбинации.

public sealed class VectorF
{
    [...]

    public static bool operator == (VectorF left, VectorI right)
    {
        // Implementation...
    }

    public static bool operator == (VectorF left, VectorF right)
    {
        // Implementation...
    }

    // Same for != operator
    [...]
}

Проблема:
Используя несколько перегрузок, я не могу просто выполнить нулевую проверку с помощью оператора ==, так как вызов будет неоднозначным.

var v = new VectorF([...]);

if (v == null)    // This call is ambiguous
[...]

Мне известно о возможности использовать приведение типов ReferenceEquals или null вместо этого, но такой подход является для меня серьезным ограничением.

var v = new VectorF([...]);

if(object.ReferenceEquals(v, null))    // Would work, is not user friendly.
[...]

if(v == (VectorF)null)    // Would also work, is neither user friendly.
[...]

Вопрос:
Есть ли способ реализовать оператор == таким образом, чтобы он допускал простую нулевую проверку и допускал проверку равенства между различными векторами?

В качестве альтернативы, есть ли другой способ, как я мог бы / должен это реализовать?


person Chillersanim    schedule 21.02.2017    source источник
comment
Кажется вероятным, что ваши классы Vector действительно должны быть неизменяемыми структурами. Тогда вопроса null не возникает. (Я предполагаю, что ваши классы содержат только 2 или 3 значения.)   -  person Matthew Watson    schedule 21.02.2017
comment
Похоже на zugzwang: красиво == или == null. Как насчет того, чтобы иметь методы для сравнения с вектором другого типа? Например. VectorF.IsSame(VectorI)? Компилятор покажет ошибку пользователя, когда он попытается vectorF == vectorI, а затем пользователь будет искать методы для сравнения, тада, проблема решена? Еще один момент: сравнение float с int не будет точным, как насчет того, чтобы сначала преобразовать VectorF в VectorI, а затем сравнить два VectorI?   -  person Sinatr    schedule 21.02.2017
comment
зачем вам нужны перегрузки влево-вправо .. не могли бы вы поделиться с нами содержимым методов перегрузки ..   -  person levent    schedule 21.02.2017
comment
Я думаю, вы можете значительно упростить свои проблемы, добавив неявное преобразование из VectorI в VectorF.   -  person Steve    schedule 21.02.2017
comment
@levent: я просто привык вызывать переменные влево и вправо, я перенял это из других перегрузок, где порядок может быть важен (например, при умножении матриц).   -  person Chillersanim    schedule 21.02.2017
comment
@Sinatr: Ваш подход к использованию другого метода звучит хорошо, хотя только если есть относительно огромные накладные расходы на преобразование из VectorI в VectorF. Как InBetween описал в своем ответе, преобразование ценности происходит в любом случае. Я думаю, что ваш подход был бы хорош, если бы вектор был поддержан, например, массивом.   -  person Chillersanim    schedule 21.02.2017


Ответы (3)


Для начала я бы отказался от всего дизайна. Я бы никогда не реализовал == с семантикой значений между разными типами, я бы нашел это довольно запутанным: instaceTypedA == instanceTypedB вопит эталонное равенство (по крайней мере, для меня).

Если вам нужно, чтобы это работало, реализуйте неявное преобразование между VectorI и VectorF. Так работает фреймворк. Когда вы сделаете следующее:

int i = 1;
double d = 1;
var b = i == d;

Oveload ==(int, double) не создается волшебным образом. Происходит то, что i неявно преобразуется в double и вызывается ==(double, double).

person InBetween    schedule 21.02.2017
comment
Спасибо. Вы правы, == должно быть ссылочным равенством. Я не думал о неявном преобразовании, так как хотел, чтобы вызов был максимально оптимизирован. Однако, поскольку значения все равно будут преобразованы, это не имеет большого значения. - person Chillersanim; 21.02.2017

Вы можете изменить сравнение, используя is:

if (v is VectorF)

Эта проверка завершится неудачно, если v равно null.

person Patrick Hofman    schedule 21.02.2017
comment
Мне это решение не очень нравится. Семантика того, что вы действительно делаете, скрыта хитрым трюком; Честно говоря, читая этот код, совсем не ясно, что вы проверяете null. - person InBetween; 21.02.2017
comment
Что ж, довольно часто используется проверка типа с использованием is (или as), хотя не все знают, что она также выполняет нулевую проверку. Как только вы узнаете, что совершенно очевидно, что null не принадлежит к определенному типу. - person Patrick Hofman; 21.02.2017
comment
В чем улучшение по сравнению с object.ReferenceEquals, кроме более короткого кода? Я бы предположил, что это накладные расходы на производительность только для нулевой проверки. - person Chillersanim; 21.02.2017
comment
Думаю, особой разницы нет, но вы можете это проверить. - person Patrick Hofman; 21.02.2017

Что я бы сделал в этом случае, так это не перегружать оператор ==, а вместо этого сделать что-то вроде:

public static bool operator == (VectorF left, object right) {
    if (object.ReferenceEquals(null, right)) {
        // handle null case
    }
    VectorF rightF = right as VectorF;
    if (!object.ReferenceEquals(null, rightF)) {
        // Compare VectorF
    }
    VectorI rightI = right as VectorI;
    if (!object.ReferenceEquals(null, rightI)) {
        // Compare VectorI
    }
    // and so on...
}
person Paolo Tedesco    schedule 21.02.2017
comment
Фу, жертвовать безопасностью типов во время компиляции, чтобы не повторять нулевую проверку? - person Ben Voigt; 21.02.2017