Поиск

Синтаксис и пример

В синтаксисе нестандартного преобразования применяется ключевое слово operator.
public static implicit operator резулътирующий_тип ( исх_тип операнд); public"static explicit operator результирующий_mun ( ucxjnun операнд).
Правил, связанных с синтаксисом преобразований, немного.

  • Любой метод преобразования для struct или class (вы можете определить их сколько хотите) должен быть static.
  • Преобразование должно быть определено или как неявное, или как явное. Ключевое слово implicit означает, что клиенту не нужно будет приводить тип — это будет сделано автоматически (неявно). Ключевое слово explicit указывает, что клиент должен явно привести значение к желаемому типу.
  • Все преобразования должны или принимать (как аргумент) тип, который определит преобразование, или возвращать этот тип.
  • Как и при перегрузке операторов в методах преобразования применяется ключевое слово operator, но без добавленного к нему оператора.

Когда я впервые читал эти правила, у меня было смутное представление о том, что делать дальше. Чтобы в этом разобраться, рассмотрим пример. У нас есть две структуры (Celsius и Fahrenheit), позволяющие клиенту преобразовывать значение типа float в любую температурную шкалу. Сначала я представлю структуру Celsius и сделаю несколько замечаний, а затем вы увидите законченное работающее приложение.

struct Celsius {
public Celsius(float temp)
{
this.temp = temp;
}
public static implicit operator Celsius(float temp) {
Celsius c;
с = new Celsius(temp);
return(c); }
public static implicit operator float(Celsius c) {
return((((c.temp - 32) / 9) * 5)); }
public float temp; }

Первое, на что нужно обратить внимание, — использование структуры вместо класса. У меня не было на это особых причин, если не считать, что применение классов обходится дороже (если говорить о выделяемых ресурсах) и что здесь нет особой нужды в использовании класса, так как структуре Celsius не нужны какие-то специфические функции, присущие классам С#, например, наследование.

Далее. Я объявил конструктор с единственным аргументом типа float. Это значение хранится в члене-переменной temp. А теперь посмотрите на оператор преобразования, следующий за конструктором структуры.

Это метод, который будет вызываться, когда клиент попытается преобразовать число с плавающей точкой к Celsius или использовать такое число в методе, где должна быть структура Celsius. Этот метод ничего особенного не делает и фактически представляет собой шаблон, который можно применять в большинстве основных преобразований. Здесь я создаю экземпляр структуры Celsius и возвращаю эту структуру. В последнем методе просто производится преобразование из шкалы Фаренгейта в шкалу Цельсия.
Вот законченное приложение, включая структуру Fahrenheit.

using System;
struct Celsius {
public Celsius(float temp)
{
this.temp = temp;
}
public static implicit operator Celsius(float temp) {
Celsius c;
с = new Celsius(temp);
return(c); }
public static implicit operator float(Celsius c)
{
return((((c.temp - 32) / 9) * 5)); }
public float temp; }
struct Fahrenheit {
public Fahrenheit(float temp)
<
this.temp = temp; >
public static implicit operator Fahrenheit(float temp) {
Fahrenheit f; f = new Fahrenheit(temp); return(f); }
public static implicit operator float(Fahrenheit f)
{
return((((f.temp • 9) / 5) + 32));
>
public float temp; }
class TemplApp
{
public static void Main()
<
float t;
t=98.6F;
Console.Мг11е("Преобразование {0} в градусы Цельсия = ", t);
Console.WriteLine((Celsius)t);
t=OF;
Console.Write("npeo6pa3oeaHne {0} в градусы
Фаренгейта = ", t); Console.WriteLine((Fahrenheit)t); > }

Скомпилировав и запустив это приложение, вы получите:

Преобразование 98.6 в градусы Цельсия = 37 Преобразование 0 в градусы Фаренгейта = 32

Все это неплохо работает, и выражение (Celsius)98.6F понятней, чем вызов статического метода класса. Но учтите, что преобразующему методу можно передавать только значения типа float. Например, этот код скомпилирован не будет:

Celsius с = new Celsius(55); Console.WriteLine((Fahrenheit)c);

Кроме того, поскольку отсутствует метод для преобразования в шкалу Цельсия, который принимал бы структуру Fahrenheit (и наоборот), код предполагает, что полученное значение требует преобразования. Словом, если я вызову (Celsius)98.6F, я получу значение 37. Однако если значение затем снова передать преобразующему методу, он не будет знать, что оно уже преобразовано и логически уже представляет температуру по Цельсию — для преобразующего метода это всего лишь число с плавающей точкой. В результате значение снова будет преобразовано. Следовательно, нам нужно изменить приложение, чтобы каждая из структур принимала в качестве допустимого аргумента другую структуру.

Когда я задумал это сделать, мне стало страшно: я подумал, что это очень сложная задача. И зря! Смотрите, как все просто:

using System;
class Temperature {
public Temperature(float Temp)
<
this.temp = Temp;
}
protected float temp; public float Temp <
get {
return this.temp; } } }
class Celsius ; Temperature {
public Celsius(float Temp) : base(Temp) {}
public static implicit operator Celsius(float Temp) {
return new Celsius(Temp); }
public static implicit operator Celsius(Fahrenheit F) {
return new Celsius(F.Temp);
}
public static implicit operator float(Celsius C)
{
return((((C.temp - 32) / 9) * 5));
} }
class Fahrenheit : Temperature
{
public Fahrenheit(float Temp) : base(Temp) {}
public static implicit operator Fahrenheit(float Temp)
{
return new Fahrenheit(Temp);
}
public static implicit operator Fahrenheit(Celsius C)
{
return new Fahrenheit(C.Temp);
}
public static implicit operator float(Fahrenheit F)
{
return((((F.temp * 9) / 5) + 32));
} }
class Temp2App
{
public static void DisplayTemp(Celsius Temp)
{
Console.Write("Преобразование {0} {1} в градусы "+ "Фаренгейта = ",
Temp.ToStringO, Temp.Temp); Console.WriteLine((Fah renheit)Temp); }
public static void DisplayTemp(Fahrenheit Temp)
{
Console.Write("Преобразование {0} {1} в градусы Цельсия = ",
Temp.ToStringO, Temp.Temp); Console.WriteLine((Celsius)Temp); }
public static void Main() {
Fahrenheit f = new Fahrenheit(98.6F);
DisplayTemp(f);
Celsius с = new Celsius(OF); DisplayTemp(c); } }

Обратите внимание: я изменил типы Celsius и Fahrenheit из struct в class. Это сделано только для того, чтобы иметь два примера: один со структурами, другой — с классами. Но более важная причина — возможность совместного использования члена temp, имея классы Celsius и Fahrenheit, производные от одного базового класса Temperature. При выводе я здесь применяю унаследованный от System. Object метод ToString.

Кроме того, я добавил преобразование из одной температурной шкалы в другую. Посмотрите, как похожи оба метода преобразования в градусы Цельсия:

public static implicit operator Celsius(float temp) {
Celsius c;
с = new Celsius(temp);
return(c); }
public static implicit operator Celsius(Fahrenheit f) {
Celsius c;
с = new Celsius(f.temp);
return(c); }

Все, что нужно было сделать, — изменить передаваемый аргумент и получать температуру из передаваемого объекта, а не из жестко заданного значения типа float. Теперь вы видите, насколько просты и стереотипны методы преобразования.

Подведем итоги

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