Примеры перегрузки операторов, которые имеют смысл [закрыто]

12

Пока я изучал C #, я обнаружил, что C # поддерживает перегрузку операторов. У меня проблема с хорошим примером, который:

  1. Имеет смысл (например, добавление класса с именем овец и коров)
  2. Не является примером объединения двух строк

Примеры из библиотеки базовых классов приветствуются.

Павел Солтысяк
источник
10
Определите «смысл», пожалуйста! Серьезно, горькие и страстные дебаты именно по этому вопросу показывают, что по этому поводу существуют огромные разногласия. Многие власти отвергают перегруженных операторов, потому что их можно заставить делать совершенно неожиданные вещи. Другие отвечают, что имена методов также могут быть выбраны, чтобы быть полностью неинтуитивными, но это не причина отклонять именованные блоки кода! Вы почти наверняка не получите никаких примеров, которые обычно считаются разумными. Примеры, которые кажутся вам разумными - возможно.
Килиан Фот
Полностью согласен с @KilianFoth. В конечном счете, программа, которая компилируется, имеет смысл для компилятора. Но если перегрузка ==для умножения, это имеет смысл для меня, но может не иметь смысла для других! Это вопрос о легитимности языков программирования какого-либо объекта или мы говорим о «лучших методах кодирования»?
Дипан Мехта

Ответы:

27

Очевидными примерами соответствующей перегрузки операторов являются любые классы, которые ведут себя так же, как и числа. Таким образом, классы BigInt (как предлагает Джалайн ), комплексные числа или матричные классы (как предполагает Superbest ) имеют те же операции, что и обычные числа, поэтому очень хорошо отображаются на математические операторы, в то время как операции времени (как предлагает svick ) отображаются на подмножество из этих операций.

Чуть более отвлеченно, операторы могут быть использованы при выполнении набора как операций, так operator+может быть союз , operator-может быть дополнением и т.д. Это действительно начинает растягиваться парадигму , хотя, особенно если вы используете сложение или умножения оператора на операцию , которая ISN» т коммутативный , как вы могли бы ожидать, что они будут.

Сам C # имеет отличный пример перегрузки нечисловых операторов. Он использует +=и -=для добавления и вычитания делегатов , т.е. регистрирует и отменяет их регистрацию. Это хорошо работает , потому что +=и -=операторы работают , как можно было бы ожидать их к, и этот результат в гораздо более лаконичном коде.

Для пуриста одна из проблем со строковым +оператором состоит в том, что он не коммутативный. "a"+"b"это не то же самое, что "b"+"a". Мы понимаем это исключение для строк, потому что оно очень распространено, но как мы можем определить, будет ли использование operator+других типов коммутативным или нет? Большинство людей предполагают, что это так, если только объект не похож на строку , но вы никогда не знаете, что люди примут.

Как и со строками, слабые стороны матриц также довольно хорошо известны. Очевидно, что Matrix operator* (double, Matrix)это скалярное умножение, тогда как, например, Matrix operator* (Matrix, Matrix)это будет матричное умножение (т. Е. Матрица умножения на скалярное произведение).

Точно так же использование операторов с делегатами настолько очевидно далеко от математики, что вы вряд ли допустите эти ошибки.

Кстати, на конференции ACCU 2011 года Роджер Орр и Стив Лав представили сессию, посвященную тому, что некоторые объекты более равны, чем другие, - взгляд на многие значения равенства, ценности и идентичности . Их слайды можно загрузить , как и Приложение Ричарда Харриса о равенстве с плавающей точкой . Резюме: будь очень осторожен с operator==драконами!

Перегрузка операторов является очень мощной семантической техникой, но ее легко использовать. В идеале вы должны использовать его только в ситуациях, когда из контекста очень ясно, каково влияние перегруженного оператора. Во многих отношениях a.union(b)это яснее , чем a+bи a*bявляется гораздо более неясным , чем a.cartesianProduct(b), тем более , что результат декартова произведения будет , SetLike<Tuple<T,T>>а не SetLike<T>.

Реальные проблемы с перегрузкой операторов возникают, когда программист предполагает, что класс будет вести себя одинаково, но на самом деле он ведет себя по-другому. Такого рода семантическое столкновение - это то, что я предлагаю, важно избегать.

Марк Бут
источник
1
Вы говорите, что операторы на матрицах отображаются очень хорошо, но умножение матриц также не коммутативно. Также операторы на делегатах еще сильнее. Вы можете сделать d1 + d2для любых двух делегатов одного типа.
svick
1
@Mark: «скалярное произведение» определяется только для векторов; Умножение двух матриц называется просто «умножение матриц». Различие не только семантическое: скалярное произведение возвращает скаляр, а умножение матрицы - матрицу (и, кстати, некоммутативную) .
BlueRaja - Дэнни Пфлюгофт
26

Я удивлен, что никто не упомянул один из наиболее интересных случаев в BCL: DateTimeи TimeSpan. Вы можете:

  • добавить или вычесть два TimeSpanс, чтобы получить другойTimeSpan
  • использовать одинарный минус на, TimeSpanчтобы получить отрицательныйTimeSpan
  • вычесть два DateTimeс, чтобы получитьTimeSpan
  • добавить или вычесть TimeSpanиз, DateTimeчтобы получить другойDateTime

Другой набор операторов , которые могли бы иметь смысл на многих типов <, >, <=, >=. В BCL, например, Versionих реализует.

svick
источник
Очень реальный пример, а не педантичные теории!
SIslam
7

Первый пример, который мне приходит в голову, - это реализация BigInteger , которая позволяет работать с большими целыми числами со знаком. Проверьте ссылку MSDN, чтобы увидеть, сколько операторов было перегружено (то есть, есть большой список, и я не проверял, все ли операторы были перегружены, но это, безусловно, так)

Кроме того, поскольку я также занимаюсь Java, а Java не позволяет перегрузить операторы, писать невероятно сладко

BigInteger bi = new BigInteger(0);
bi += 10;

Чем в Java:

BigDecimal bd = new BigDecimal(0);
bd = bd.add(new BigDecimal(10));
Jalayn
источник
5

Я рад, что увидел это, потому что я дурачился с Иронией, и в ней БОЛЬШОЕ использование перегрузки операторов. Вот пример того, что он может сделать.

Итак, Irony - это «.NET Language реализации Kit» и генератор парсера (генерирующий парсер LALR). Вместо того, чтобы изучать новый синтаксис / язык, такой как генераторы синтаксических анализаторов, такие как yacc / lex, вы пишете грамматику на C # с перегрузкой оператора. Вот простая грамматика БНФ

// BNF 
Expr := Term | BinExpr
Term := number | ParExpr
ParExpr := "(" + Expr + ")"
BinExpr := number + BinOp + number
BinOp := "+" | "-" | "*" | "/"
number := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Так что это простая маленькая грамматика (извините, если есть несоответствия, так как я просто изучаю BNF и строю грамматику). Теперь давайте посмотрим на C #:

  var Expr = new NonTerminal("Expr");
  var Term = new NonTerminal("Term");
  var BinExpr = new NonTerminal("BinExpr");
  var ParExpr = new NonTerminal("ParExpr");
  var BinOp = new NonTerminal("BinOp");
  var Statement = new NonTerminal("Statement");
  var ProgramLine = new NonTerminal("ProgramLine");
  var Program = new NonTerminal("Program", typeof(StatementListNode));
  // BNF Rules - Overloading
  Expr.Rule = Term | BinExpr;
  Term.Rule = number | ParExpr;
  ParExpr.Rule = "(" + Expr + ")";
  BinExpr.Rule = Expr + BinOp + Expr;
  BinOp.Rule = ToTerm("+") | "-" | "*" | "/" | "**";

Как вы можете видеть, с перегрузкой оператора написание грамматики на C # почти точно написание грамматики на BNF. Для меня это не только имеет смысл, но и позволяет использовать перегрузку операторов.

Jetti
источник
3

Ключевым примером является оператор == / operator! =.

Если вы хотите легко сравнить два объекта по значениям данных, а не по ссылкам, вам нужно перегрузить .Equals (and.GetHashCode!) И, возможно, захотите также использовать операторы! = И == для согласованности.

Я никогда не видел диких перегрузок других операторов в C #, хотя (я предполагаю, что есть крайние случаи, где это могло бы быть полезным).

Эд Джеймс
источник
1

Этот пример из MSDN показывает, как реализовать комплексные числа и заставить их использовать обычный оператор +.

Другой пример показывает, как это сделать для добавления матрицы, а также объясняет, как не использовать ее для добавления автомобиля в гараж (читайте по ссылке).

Superbest
источник
0

Хорошее использование перегрузки может быть редким, но это случается.

перегрузка operator == и operator! = показывают две философские мысли: те, кто говорит, что это облегчает задачу, и те, кто не говорит, что это предотвращает сравнение адресов (то есть я указываю на одно и то же место в памяти, а не на одну и ту же копию). объект).

Я считаю, что перегрузка операторов приведения удобна в определенных ситуациях. Например, мне пришлось сериализовать / десериализовать в XML логическое значение, представленное как 0 или 1. Правильный (неявный или явный, я забыл) оператор приведения от логического к int и обратно сделал свое дело.

MPelletier
источник
4
Это не мешает сравнивать адреса: вы все еще можете использовать object.ReferenceEquals().
Ден04
@ dan04 Очень, очень приятно знать!
MPelletier
Другой способ сравнить адреса - заставить использование объекта ==путем приведения: (object)foo == (object)barвсегда сравнивает ссылки. Но я бы предпочел ReferenceEquals(), как упоминает @ dan04, потому что это более понятно, что он делает.
svick
0

Они не относятся к той категории вещей, о которой обычно думают люди, когда им приходится перегружать операторы, но я думаю, что одним из наиболее важных операторов, способных перегрузить, является оператор преобразования .

Операторы преобразования особенно полезны для типов значений, которые могут «очищать» от числового типа или могут действовать как числовой тип в некоторых контекстах. Например, вы можете определить специальный Idтип, который представляет определенный идентификатор, и вы можете предоставить неявное преобразование в, intчтобы вы могли передать Idметод, который принимает int, но явное преобразование из intв, Idчтобы никто не мог передать intв метод, который принимает Idбез приведения его первым.

В качестве примера за пределами C # язык Python включает в себя множество специальных поведений, которые реализованы как перегружаемые операторы. К ним относятся inоператор для проверки членства, ()оператор для вызова объекта, как будто это функция, и lenоператор для определения длины или размера объекта.

Кроме того, у вас есть языки, такие как Haskell, Scala и многие другие функциональные языки, где такие имена, как +обычные функции, а не операторы вообще (и есть языковая поддержка для использования функций в позиции инфикса).

Даниэль Приден
источник
0

Точка Struct в System.Drawing пространстве имен использует перегрузку для сравнения два разных мест , используя перегрузку оператора.

 Point locationA = new Point( 50, 50 );
 Point locationB = new Point( 50, 50 );

 if ( locationA == locationB )
    Console.WriteLine( "Their locations are the same" );
 else
    Console.WriteLine( "Their locations  are different" );

Как видите, намного проще сравнить координаты X и Y двух мест с использованием перегрузки.

Картик Сринивасан
источник
0

Если вы знакомы с математическим вектором, вы можете увидеть использование в перегрузке +оператора. Вы можете добавить вектор a=[1,3]с b=[2,-1]и получить c=[3,2].

Перегрузка equals (==) также может быть полезна (хотя, вероятно, лучше реализовать equals()метод). Чтобы продолжить векторные примеры:

v1=[1,3]
v2=[1,3]
v1==v2 // True
MartinHaTh
источник
-2

Представьте кусок кода для рисования на форме

{
  Point p = textBox1.Location;
  Size dp = textBox1.Size;

  // Here the + operator has been overloaded by the CLR
  p += dp;  // Now p points to the lower right corner of the textbox.
  ..
}

Другой распространенный пример - когда структура используется для хранения информации о положении в форме вектора.

public struct Pos
{
    public double x, y, z;
    public double Distance { get { return Math.Sqrt(x * x + y * y + z * z); } }
    public static Pos operator +(Pos A, Pos B)
    {
        return new Pos() { x = A.x + B.x, y = A.y + B.y, z = A.z + B.z };
    }
    public static Pos operator -(Pos A, Pos B)
    {
        return new Pos() { x = A.x - B.x, y = A.y - B.y, z = A.z - B.z };
    }
}

только для последующего использования в качестве

{
    Pos A = new Pos() { x = 4, y = -1, z = 0.5 };
    Pos B = new Pos() { x = 8, y = 2, z = 1.5 };

    double x = (B - A).Distance;
}
ja72
источник
4
Вы добавляете векторы, а не позиции: \ Это хороший пример того , когда operator+следует не перегружать (вы можете реализовать точку в терминах вектора, но вы не должны быть в состоянии добавить две точки)
BlueRaja - Дэнни Pflughoeft
@ BlueRaja-DannyPflughoeft: Добавление позиций для получения другой позиции не имеет смысла, но вычитание их (для получения вектора) делает, как и их усреднение . Можно вычислить среднее значение p1, p2, p3 и p4 через p1+((p2-p1)+(p3-p1)+(p4-p1))/4, но это выглядит несколько неловко.
суперкат
1
В аффинной геометрии вы можете выполнять алгебру с точками и линиями, например, сложение, масштабирование и т. Д. Реализация, однако, требует однородных координат, которые в любом случае обычно используются в трехмерной графике. Сложение двух точек фактически приводит к их среднему значению.
ja72