Дополнительные возможности ООП в C#

Определение операторов

Последнее обновление: 28.12.2021

Наряду с методами в классах и структурах мы можем также определять операторы. Например, пусть у нас есть следующий класс Counter:

class Counter
{
	public int Value { get; set; }
}

Данный класс представляет некоторый счетчик, значение которого хранится в свойстве Value.

И допустим, у нас есть два объекта класса Counter - два счетчика, которые мы хотим сравнивать или складывать на основании их свойства Value, используя стандартные операции сравнения и сложения:

Counter counter1 = new Counter { Value = 23 };
Counter counter2 = new Counter { Value = 45 };

bool result = counter1 > counter2;
Counter c3 = counter1 + counter2;

Но на данный момент ни операция сравнения, ни операция сложения для объектов Counter не доступны. Эти операции могут использоваться для ряда примитивных типов. Например, по умолчанию мы можем складывать числовые значения, но как складывать объекты комплексных типов - классов и структур компилятор не знает. И для этого нам надо выполнить перегрузку нужных нам операторов.

Определение операторов заключается в определении в классе, для объектов которого мы хотим определить оператор, специального метода:

public static возвращаемый_тип operator оператор(параметры)
{  }

Этот метод должен иметь модификаторы public static, так как перегружаемый оператор будет использоваться для всех объектов данного класса. Далее идет название возвращаемого типа. Возвращаемый тип представляет тот тип, объекты которого мы хотим получить. К примеру, в результате сложения двух объектов Counter мы ожидаем получить новый объект Counter. А в результате сравнения двух мы хотим получить объект типа bool, который указывает истинно ли условное выражение или ложно. Но в зависимости от задачи возвращаемые типы могут быть любыми.

Затем вместо названия метода идет ключевое слово operator и собственно сам оператор. И далее в скобках перечисляются параметры. Бинарные операторы принимают два параметра, унарные - один параметр. И в любом случае один из параметров должен представлять тот тип - класс или структуру, в котором определяется оператор.

Например, перегрузим ряд операторов для класса Counter:

class Counter
{
	public int Value { get; set; }
        
	public static Counter operator +(Counter counter1, Counter counter2)
    {
		return new Counter { Value = counter1.Value + counter2.Value };
	}
	public static bool operator >(Counter counter1, Counter counter2)
	{
		return counter1.Value > counter2.Value;
	}
	public static bool operator <(Counter counter1, Counter counter2)
	{
		return counter1.Value < counter2.Value;
	}
}

Поскольку все определенные операторы - бинарные - то есть проводятся над двумя объектами, то для каждой перегрузки предусмотрено по два параметра.

Так как в случае с операцией сложения мы хотим сложить два объекта класса Counter, то оператор принимает два объекта этого класса. И так как мы хотим в результате сложения получить новый объект Counter, то данный класс также используется в качестве возвращаемого типа. Все действия этого оператора сводятся к созданию, нового объекта, свойство Value которого объединяет значения свойства Value обоих параметров:

public static Counter operator +(Counter counter1, Counter counter2)
{
	return new Counter { Value = counter1.Value + counter2.Value };
}

Также определены две операции сравнения. Если мы определяем одну из этих операций сравнения, то мы также должны определить вторую из этих операций. Сами операторы сравнения сравнивают значения свойств Value и в зависимости от результата сравнения возвращают либо true, либо false.

Теперь используем перегруженные операторы в программе:

Counter counter1 = new Counter { Value = 23 };
Counter counter2 = new Counter { Value = 45 };
bool result = counter1 > counter2;
Console.WriteLine(result); // false

Counter counter3 = counter1 + counter2;
Console.WriteLine(counter3.Value);  // 23 + 45 = 68

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

public static int operator +(Counter counter1, int val)
{
	return counter1.Value + val;
}

Данный метод складывает значение свойства Value и некоторое число, возвращая их сумму. И также мы можем применить этот оператор:

Counter counter1 = new Counter { Value = 23 };
int result = counter1 + 27; // 50
Console.WriteLine(result);

Следует учитывать, что не все операторы можно определить. В частности, мы можем определить логику для следующих операторов:

  • унарные операторы +x, -x, !x, ~x, ++, --, true, false

  • бинарные операторы +, -, *, /, %

  • операции сравнения ==, !=, <, >, <=, >=

  • поразрядные операторы &, |, ^, <<, >>

  • логические операторы &&, ||

Кроме того, есть несколько операторов, которые надо определять парами:

  • == и !=

  • < и >

  • <= и >=

И есть ряд операторов, которые нельзя перегрузить, например, операцию равенства = или тернарный оператор ?:, а также ряд других. Полный список перегружаемых операторов можно найти в документации msdn

Определение инкремента и декремента

Следует учитывать, что в коде оператора не должны изменяться те объекты, которые передаются в оператор через параметры. Например, мы можем определить для класса Counter оператор инкремента:

public static Counter operator ++(Counter counter1)
{
	counter1.Value += 10;
	return counter1;
}

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

И более корректное определение оператора инкремента будет выглядеть так:

public static Counter operator ++(Counter counter1)
{
	return new Counter { Value = counter1.Value + 10 };
}

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

Counter counter1 = new Counter() { Value = 10 };
Counter counter2 = counter1++;
Console.WriteLine(counter1.Value);      // 20
Console.WriteLine(counter2.Value);      // 10

Counter counter3 = ++counter1;
Console.WriteLine(counter1.Value);      // 30
Console.WriteLine(counter3.Value);      // 30

При операции постфиксного инкремента (counter1++) компилятор сначала создает временную переменную, в которую сохраняет текущий объект. Затем текущий объект замещает значением, полученным из функции оператора. В качестве результата операции возвращается значение временной переменной. При префиксном инкременте (++counter1) компилятор возвращает новое значение, полученное из функции оператора.

Определение операций true и false

Отдельно стоит отметить определение операторов true и false. Эти операторы определяются, когда мы хотим использовать объект типа в качестве условия. Например, определим данные операторы в классе Counter:

class Counter
{
	public int Value { get; set; }
	
	public static bool operator true(Counter counter1)
    {
		return counter1.Value != 0;
    }
	public static bool operator false(Counter counter1)
    {
		return counter1.Value == 0;
    }
}

Например:

Counter counter = new Counter() { Value = 0 };
if (counter)
	Console.WriteLine(true);
else
	Console.WriteLine(false);

Также стоит отметить, что если мы хотим использовать операцию отрицания, типа if (!counter), то нам также необходимо определить для типа операцию !:

Counter counter = new Counter() { Value = 2 };
if (!counter)
    Console.WriteLine(true);
else
    Console.WriteLine(false);

class Counter
{
    public int Value { get; set; }

    public static bool operator !(Counter counter1)
    {
        return counter1.Value == 0;
    }

    public static bool operator true(Counter counter1)
    {
        return counter1.Value != 0;
    }
    public static bool operator false(Counter counter1)
    {
        return counter1.Value == 0;
    }
}

Операция отрицания фактически синонимична операции false, поэтому содержит аналогичное условие.

Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850