Введение в программирование на C# 2.0

Перегруженные операции

Показывать лекцию целиком

Основная конструкция C# – объявление класса. Класс есть тип. Тип характеризуется неизменяемым набором свойств и методов. Для предопределенных типов определены множества операций, которые кодируются с использованием множества определенных в языке символов операций.

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

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

Для построения сложных выражений на основе элементарных выражений производных типов C# предоставляет те же возможности, что и для выражений предопределенных типов. Главная проблема заключается в том, что алгоритм вычисления значения выражения, представленного операндами вновь объявляемого типа в сочетании с символами операций, применяемых с операндами данного типа, неизвестен. Правила вычисления значения для такого выражения должны быть специальным образом определены программистом при определении класса. Эти правила (алгоритм вычисления значения выражения) задаются при определении функций специального вида, выражения вызова которых внешне напоминают выражения, построенные на основе операндов предопределенного типа и соответствующих символов операций.

Перегрузка операций

Перегрузка операций в C# является способом объявления семантики новых операций, которые обозначаются принятыми в C# символами операций.

Перегрузка операций строится на основе общедоступных ( public ) статических (вызываемых от имени класса) функций-членов с использованием ключевого слова operator.

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

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

+, –, !, ~, ++, ––, true, false Унарные символы операций, допускающие перегрузку. true и false также являются операциями
+, –, *, /, %, , |, ^, <<, >> Бинарные символы операций, допускающие перегрузку
==, !=, <, >, <=, >= Операции сравнения перегружаются
, || Условные логические операции моделируются с использованием ранее переопределенных операций и |
[] Операции доступа к элементам массивов моделируются за счет индексаторов
() Операции преобразования реализуются с использованием ключевых слов implicit и explicit
+=, –=, *=, /=, %=, =, =, ^=, <<=, >>= Операции не перегружаются, по причине невозможности перегрузки операции присвоения
=, ., ?:, –>, new, is, sizeof, typeof Операции, не подлежащие перегрузке

При этом при перегрузке операций для любого нового типа выполняются следующие правила:

  • префиксные операции ++ и –– перегружаются парами;
  • операции сравнения перегружаются парами: если перегружается операция ==, также должна перегружаться операция !=. То же самое относится к парам < и >, <= и >=.
  • Операторная функция. Объявление

    Перегрузка операций основывается на следующих принципах:

  • определяется статическая операторная функция особого вида (синтаксис объявления рассматривается ниже), в заголовке которой указывается символ переопределяемой операции;
  • в теле функции реализуется соответствующий алгоритм, определяющий семантику операторной функции (семантику новой операции);
  • выражение вызова операторной функции имитирует внешний вид выражения, построенного на основе соответствующего символа операции;
  • при трансляции выражения, включающего символы операций, производится анализ типов операндов данного выражения, на основе которого идентифицируется соответствующая операторная функция, которой и обеспечивается передача управления (возможно, что при этом автоматически реконструируется соответствующее выражение вызова операторной функции).
  • Синтаксис объявления операторных функций представлен в виде множества БНФ:

    operator–declaration ::= 
    attributes opt operator–modifiers operator–declarator operator–body 
    
    operator–modifiers 	::= operator–modifier
                     	::= operator–modifiers operator–modifier
    
    operator–modifier 	::= public | static | extern 
    operator–declarator	::= unary–operator–declarator
                     	::= binary–operator–declarator
                     	::= conversion–operator–declarator 
    
    unary–operator–declarator ::=
       type operator overloadable–unary–operator (type identifier)
    overloadable–unary–operator   ::= + 
                                ::= –
                                ::= !
                                ::= ~
                                ::= ++
                                ::= ––
                                ::= true
                                ::= false 
    
    binary–operator–declarator ::= 
       type operator overloadable–binary–operator (type identifier , type identifier) 
    overloadable–binary–operator    ::= +
                                 ::= –
                                 ::= *
                                 ::= /
                                 ::= %
                                 ::= 
                                 ::= |
                                 ::= ^
                                 ::= <<
                                 ::= >>
                                 ::= ==
                                 ::= !=
                                 ::= >
                                 ::= <
                                 ::= >=
                                 ::= <=
    
    conversion–operator–declarator ::= implicit operator type (type identifier)
                          ::= explicit operator type (type identifier) 
    
    operator–body ::= block
                 ::= ;

    Унарные операторные функции. Пример объявления и вызова

    using System;
    using System.Collections.Generic;
    using System.Text;
    
    namespace operations
    {
     // Для моделирования унарных постфиксных и префиксных операций может
     // потребоваться модификация структуры класса: объявляются дополнительные
     // переменные для сохранения значений координат.
    
     public class Point2D
     {
      public float x,    y;
             float xTmp, yTmp;
      public Point2D()
      {
       x    = 0.0;
       y    = 0.0;
       xTmp = 0.0;
       yTmp = 0.0;
      }
    
      public static Point2D operator ++(Point2D par)
      { 
      // Фактическим координатам присваиваются старые значения.
      par.x = (par.xTmp)++;
      par.y = (par.yTmp)++;
    
      return par;
      }
    
      public static Point2D operator --(Point2D par)
      {
      // Фактическим координатам присваиваются новые значения.
      par.x = --(par.xTmp);
      par.y = --(par.yTmp);
    
      return par;
      }
      }
    
      class Program
      {
      static void Main(string[] args)
      {
      Point2D p = new Point2D();
      int i;
    
      // При этом в соответствии с объявлением,
      // унарный плюс всегда постфиксный,
      // а унарный минус всегда префиксный. 
      for (i = 0; i < 10; i++)
      {
       p++;
       Console.WriteLine("{0:F3},{1:F3}",p.x, p.y);
      }
                Console.WriteLine("============================================");
    for (i = 0; i < 10; i++)
    {
    ++p;
    Console.WriteLine("{0:F3},{1:F3}", p.x, p.y);
    }
    Console.WriteLine("============================================");
    for (i = 0; i < 10; i++)
    {
    p--;
    Console.WriteLine("{0:F3},{1:F3}", p.x, p.y);
    }
                Console.WriteLine("============================================");
    for (i = 0; i < 10; i++)
    {
    --p;
    Console.WriteLine("{0:F3},{1:F3}", p.x, p.y);
    }
    }
    }
    }

    Бинарные операции. Пример объявления и вызова

    Семантика перегружаемой операторной функции определяется решаемыми задачами и фантазией разработчика:

    // Бинарные операции также обязаны возвращать значения!
    public static Point2D operator + (Point2D par1, Point2D par2)
    {
    return new Point2D(par1.x+par2.x,par1.y+par2.y);
    }
    
    // Реализуется алгоритм "сложения" значения типа Point2D
    // со значением типа float.
    // От перемены мест слагаемых сумма НЕ ИЗМЕНЯЕТСЯ.
    // Однако эта особенность нашей
    // операторной функции "сложения" (операции "сложения") должна быть 
    // прописана программистом.
    // В результате получаем ПАРУ операторных функций, которые отличаются
    // списками параметров. 
    // Point2D + float	
    public static Point2D operator + (Point2D par1, float val)
    {
    return new Point2D(par1.x+val,par1.y+val);
    }
    
    // float + Point2D	
    public static Point2D operator + (float val, Point2D par1)
    {
    return new Point2D(val+par1.x,val+par1.y);
    }

    А вот применение этих функций. Внешнее сходство выражений вызова операторных функций с обычными выражениями очевидно. И при этом иного способа вызова операторных функций нет!

    ...p1 + p2...
    ...3.14 + p2...
    ...p2 + 3.14...

    Операции сравнения реализуются по аналогичной схеме. Хотя не существует никаких ограничений на тип возвращаемого значения, в силу специфики применения (обычно в условных выражениях операторов управления) операций сравнения все же имеет смысл определять их как операторные функции, возвращающие значения true и false:

    public static bool operator == (myPoint2D par1, myPoint2D par2)
    {
        if ((par1.x).Equals(par2.x)  (par1.y).Equals(par2.y))
    	return true;
        else
    	return false;
    }
    
    public static bool operator != (myPoint2D par1, myPoint2D par2)
    {
    if (!(par1.x).Equals(par2.x) || !(par1.y).Equals(par2.y))
    	return true;
    else
    	return false;
    }
    operator true и operator false

    В известном мультфильме о Винни Пухе и Пятачке, Винни делает заключение относительно НЕПРАВИЛЬНОСТИ пчел. Очевидно, что по его представлению объекты — представители ДАННОГО класса пчел НЕ удовлетворяют некоторому критерию.

    В программе можно поинтересоваться непосредственно значением некоторого поля объекта:

    Point2D p1 = new Point2D(GetVal(), GetVal());
     ::::::::::
    // Это логическое выражение!
    if ((p1.x).Equals(125)) {/*...*/}

    Так почему же не спросить об этом у объекта напрямую?

    Хотя бы так:

    // Критерий истинности объекта зависит от разработчика.
    // Если значение выражения в скобках примет значение true,
    // то пчела окажется правильной!  
    if (p1) {/*...*/}

    В классе может быть объявлена операция (операторная функция) true, которая возвращает значение true типа bool для обозначения факта true и возвращает false в противном случае.

    Классы, включающие объявления подобных операций (операторных функций), могут быть использованы в структуре операторов if, do, while, for в качестве условных выражений.

    При этом, если в классе была определена операция true, в том же классе должна быть объявлена операция false:

    // Перегрузка булевских операторов. Это ПАРНЫЕ операторы.
    // Объекты типа Point2D приобретают способность судить о правде и лжи! 
    // А что есть истина? Критерии ИСТИННОСТИ (не путать с истиной)
    // могут быть самые разные. В частности, степень удаления от точки
    // с координатами (0,0). 
    public static bool operator true (Point2D par)
    {
    if (Math.Sqrt(par.x*par.x + par.y*par.y) < 10.0) return true;
    else                                         	return false;
    }
    
    public static bool operator false (Point2D par)
    {
    double r = (Math.Sqrt(par.x*par.x + par.y*par.y));  
    if (r > 10.0 || r.Equals(10.0))               	return false;
    else                                        	return true;
    }

    Определение операций. Конъюнкция и дизъюнкция

    После того как объявлены операторы true и false, могут быть объявлены операторные функции — конъюнкция и дизъюнкция. Эти функции работают по следующему принципу:

  • после оценки истинности (критерий оценки задается при объявлении операций true и false ) операндов конъюнкции или дизъюнкции функция возвращает ссылку на один из операндов либо на вновь создаваемый в функции объект;
  • в соответствии с ранее объявленными операциями true и false, операторные конъюнкция и дизъюнкция возвращают логическое значение, соответствующее отобранному или вновь созданному объекту:
  • public static Point2D operator | (Point2D par1, Point2D par2)
    {
    if (par1) return par1; // Определить "правильность" объекта par1 можем!
    if (par2) return par2; // Определить "правильность" объекта par2 можем!
    return new Point2D(10.0F, 10.0F); // Вернули ссылку на
                                   // новый "неправильный" объект.
    }
    
    public static Point2D operator  (Point2D par1, Point2D par2)
    {
    if (par1  par2) return par1; // Вернули ссылку на один из "правильных"
                                   // объектов. 
    else return new Point2D(10.0F, 10.0F); // Вернули ссылку на
                                   // новый "неправильный" объект. 
    }

    Выражение вызова операторной функции "дизъюнкция" имеет вид

    if (p0 | p1) Console.WriteLine("true!");

    А как же || и ?

    Эти операции сводятся к ранее объявленным операторным функциям.

    Обозначим символом T тип, в котором была объявлена данная операторная функция.

    Если при этом операнды операций или || являются операндами типа T и для них были объявлены соответствующие операторные функции operator () и/или operator |(), то для успешной эмуляции операций или || должны выполняться следующие условия:

  • тип возвращаемого значения и типы каждого из параметров данной операторной функции должны быть типа T. Операторные функции operator и operator |, определенные на множестве операндов типа T, должны возвращать результирующее значение типа T ;
  • к результирующему значению применяется объявленная в классе T операторная функция operator true (operator false).
  • При этом приобретают смысл операции или ||. Их значение вычисляется в результате комбинации операторных функций operator true() или operator false() со следующими операторными функциями:

  • Операция x y представляется в виде выражения, построенного на основе трехместной операции
    T.false(x)? x: T.(x, y),

    где T.false(x) является выражением вызова объявленной в классе операторной функции false, а T.(x, y) – выражением вызова объявленной в классе T операторной функции .

    Таким образом, сначала определяется "истинность" операнда x, и если значением соответствующей операторной функции является ложь, результатом операции оказывается значение, вычисленное для x. В противном случае определяется "истинность" операнда y, и результирующее значение определяется как КОНЪЮНКЦИЯ истинностных значений операндов x и y.

  • Операция x || y представляется в виде выражения, построенного на основе трехместной операции
    T.true(x)? x: T.|(x, y),

    где T.true(x) является выражением вызова объявленной в классе операторной функции, а T.|(x, y) – выражением вызова объявленной в классе T операторной функции |.

    Таким образом, сначала определяется "истинность" операнда x, и если значением соответствующей операторной функции является истина, результатом операции оказывается значение, вычисленное для x. В противном случае определяется "истинность" операнда y, и результирующее значение определяется как ДИЗЪЮНКЦИЯ истинностных значений операндов x и y.

  • При этом в обоих случаях "истинностное" значение x вычисляется один раз, а значение выражения, представленного операндом y, не вычисляется вообще либо определяется один раз.

    И вот результат...

    using System;
    
    namespace ThisInConstruct
    {
    class Point2D
    {
    private float x, y, xTmp, yTmp;
    public float X
    {
    get
    {
    return x;
    }	
    } 
    
    public float PropertyY
    {
    get
    {
    return y;
    }
    set
    {
    string ans = null;
    Console.Write
    ("Are You sure to change the y value of object of Point2D? (y/n) >> ");
    ans = Console.ReadLine(); 
    if (ans.Equals("Y") || ans.Equals("y"))
    { 
    y = value;
    Console.WriteLine("The value y of object of Point2D changed...");
    }
    }	
    } 
    
    public Point2D(float xKey, float yKey)
    {
    Console.WriteLine("Point2D({0}, {1}) is here!", xKey, yKey);
    // Какой-нибудь сложный обязательный
    // код инициализации данных-членов класса.
    int i = 0;
    
    xTmp = xKey;
    yTmp = yKey;
    
    while (i < 100)
    {
    x = xKey;
    y = yKey;
    
    i++;
    }	
    }
    
    // А все другие конструкторы в обязательном порядке предполагают
    // регламентные работы по инициализации значений объекта – и делают при этом
    // еще много чего...
    public Point2D():this(0.0F,0.0F)
    {
    int i;
    for (i = 0; i < 100; i++)
    {
    // Хорошо, что значения уже проинициализированы! 
    // Здесь своих проблем хватает.
    }
    
    Console.WriteLine("Point2D() is here!");
    }
    
    public Point2D(Point2D pKey):this(pKey.x, pKey.y)
    {
    int i;
    for (i = 0; i < 100; i++)
    {
    // Хорошо, что значения уже проинициализированы! 
    // Здесь своих проблем хватает.
    }
    
    Console.WriteLine("Point2D({0}) is here!", pKey.ToString());
    }
    // Перегруженные операции обязаны возвращать значения!
    // Операторные функции! Must be declared static and public.
    // Префиксная и постфиксная формы ++ и –— не различаются по результату
    // выполнения. Тем не менее они здесь реализуются:
    // одна как постфиксная…
    public static Point2D operator ++ (Point2D par)
    {
    par.x = (par.xTmp)++;
    par.y = (par.yTmp)++;
    return par;
    }
    
    // другая – как префиксная …
    public static Point2D operator -- (Point2D par)
    {
    par.x = --(par.xTmp);
    par.y = --(par.yPmp);
    return par;
    }
    
    // Бинарные операции также обязаны возвращать значения!
    public static Point2D operator + (Point2D par1, Point2D par2)
    {
    return new Point2D(par1.x+par2.x,par1.y+par2.y);
    }
    
    // От перемены мест слагаемых сумма ... :
    // Point2D + float	
    public static Point2D operator + (Point2D par1, float val)
    {
    return new Point2D(par1.x+val,par1.y+val);
    }
    
    // float + Point2D	
    public static Point2D operator + (float val, Point2D par1)
    {
    return new Point2D(val+par1.x,val+par1.y);
    }
    
    // Перегрузка булевских операторов. Это ПАРНЫЕ операторы.
    // Объекты типа Point2D приобретают способность судить об истине и лжи! 
    // А что есть истина? Критерии ИСТИННОСТИ (не путать с истиной)
    // могут быть самые разные. 
    public static bool operator true (Point2D par)
    {
    if (Math.Sqrt(par.x*par.x + par.y*par.y) < 10.0) return true;
    else                                             return false;
    }
    
    public static bool operator false (Point2D par)
    {
    double r = (Math.Sqrt(par.x*par.x + par.y*par.y));  
    if (r > 10.0 || r.Equals(10.0))                  return false;
    else                                             return true;
    }
    //========================================================================
    
    public static Point2D operator | (Point2D par1, Point2D par2)
    {
    if (par1) return par1;
    if (par2) return par2;
    else return new Point2D(10.0F, 10.0F); 
    }
    
    public static Point2D operator  (Point2D par1, Point2D par2)
    {
    if (par1  par2) return par1;
    else return new Point2D(10.0F, 10.0F); 
    }
    }
    
    
    // Стартовый класс.
    
    
    class C1
    {
    
    static void Main(string[] args)
    {
    Console.WriteLine("__________");
    Point2D p0 = new Point2D();
    Console.WriteLine("__________");
    Point2D p1 = new Point2D(GetVal(), GetVal());
    Console.WriteLine("__________");
    Point2D p2 = new Point2D(p1);
    Console.WriteLine("__________");
    
    // Меняем значение y объекта p1...
    Console.WriteLine("**********");
    p1.PropertyY = GetVal();
    Console.WriteLine("**********");
    
    Console.WriteLine("p0.x == {0}, p0.y == {1}", p0.X,p0.PropertyY);
    Console.WriteLine("p1.x == {0}, p1.y == {1}", p1.X,p1.PropertyY);
    Console.WriteLine("p2.x == {0}, p2.y == {1}", p2.X,p2.PropertyY);
    Console.WriteLine("##########");
    p0++;
    ++p1;
    // После объявления операторных функций true и false объекты класса Point2D
    // могут включаться в контекст условного оператора и оператора цикла. 
    if (p1) Console.WriteLine("true!");
    else Console.WriteLine("false!");
    
    // Конъюнкции и дизъюнкции. Выбирается объект p0 или p1,
    // для которого вычисляется истинностное значение.
    if (p0 | p1) Console.WriteLine("true!");
    if (p0  p1) Console.WriteLine("true!");
    if (p0 || p1) Console.WriteLine("true!");
    if (p0  p1) Console.WriteLine("true!");
    
    for ( ; p2; p2++)
    {
    Console.WriteLine("p2.x == {0}, p2.y == {1}", p2.X,p2.PropertyY);
    }
    
    
    Console.WriteLine("p0.x == {0}, p0.y == {1}", p0.X,p0.PropertyY);
    Console.WriteLine("p1.x == {0}, p1.y == {1}", p1.X,p1.PropertyY);
    Console.WriteLine("p2.x == {0}, p2.y == {1}", p2.X,p2.PropertyY);
    }
    
    public static float GetVal()
    {
    float fVal;
    string str = null;
    
    while (true)
    {
    try
    {
    Console.Write("float value, please >> ");
    str = Console.ReadLine();
    fVal = float.Parse(str);
    return fVal;
    }
    catch
    {
    Console.WriteLine("This is not a float value...");
    }	
    }
    }
    }
    }

    Пример. Свойства и индексаторы

    using System;
    namespace PointModel
    {
    class SimplePoint
    {
    float x, y;
    public SimplePoint[] arraySimplePoint = null;
    
    public SimplePoint()
    {
    x = 0.0F;
    y = 0.0F;	 
    }
    
    public SimplePoint(float xKey, float yKey): this()
    {
    x = xKey;
    y = yKey;	
    }
    
    public SimplePoint(SimplePoint PointKey):this(PointKey.x, PointKey.y)
    {
    }
    
    public SimplePoint(int N)
    {
    int i;
    if (N > 0  arraySimplePoint == null)
    {
    arraySimplePoint = new SimplePoint[N];
    for (i = 0; i < N; i++)
    {
     arraySimplePoint[i] = new SimplePoint((float)i, (float)i); 
    }
    }	
    }
    }
    
    class MyPoint
    {
    float x, y;
    MyPoint[] arrayMyPoint = null;
    
    int arrLength = 0;
    int ArrLength
    {
    get
    {
    return arrLength;
    }
    set
    {
    arrLength = value;
    }
    }
    
    bool isArray = false;
    bool IsArray // Это свойство только для чтения!
    {
    get
    {
    return isArray;
    }
    }
    
    MyPoint()
    {
    x = 0.0F;
    y = 0.0F;	 
    }
    
    public MyPoint(float xKey, float yKey): this()
    {
    x = xKey;
    y = yKey;	
    }
    
    public MyPoint(MyPoint PointKey): this(PointKey.x, PointKey.y)
    {
    }
    
    public MyPoint(int N)
    {
    int i;
    if (N > 0  IsArray == false)
    {
    this.isArray = true;
    this.arrLength = N;
    arrayMyPoint = new MyPoint[N];
    for (i = 0; i < N; i++)
    {
    arrayMyPoint[i] = new MyPoint((float)i, (float)i); 
    }
    }	
    }
    
    bool InArray(int index)
    {
    if (!IsArray) return false;
    if (index >= 0  index < this.ArrLength) return true;
    else return false;	
    }
    
    // Внимание! Объявление индексатора.
    // Индексатор НЕ является операторной функцией.
    // Он объявляется как НЕСТАТИЧЕСКОЕ множество операторов
    // со стандартным заголовком. При его объявлении используется
    // ключевое слово this. 
    public MyPoint this[int index]
    {
    get
    {
    if (IsArray == false) return null;
    if (InArray(index)) return arrayMyPoint[index];
    else return null; 
    }
    set
    {
    if (IsArray == false) return;
    if (InArray(index))
    {
    	arrayMyPoint[index].x = value.x;
    	arrayMyPoint[index].y = value.y;
    }
    else return; 
    }	
    }	
    
    // Объявление еще одного (перегруженного!) индексатора.
    // В качестве значения для индексации (параметра индексации)
    // используется символьная строка.
    public MyPoint this[string strIndex]
    {
    get
    {
    int index = int.Parse(strIndex);
    if (IsArray == false) return null;
    if (InArray(index)) return arrayMyPoint[index];
    else return null; 
    }
    set
    {
    int index = int.Parse(strIndex);
    if (IsArray == false) return;
    if (InArray(index))
    {
    	arrayMyPoint[index].x = value.x;
    	arrayMyPoint[index].y = value.y;
    }
    else return; 
    }	
    }
    }
    
    class Class1
    {
    
    static void Main(string[] args)
    {
    SimplePoint spArr = new SimplePoint(8);
    SimplePoint wsp = new SimplePoint(3.14F, 3.14F);
    SimplePoint wsp0;
    spArr.arraySimplePoint[3] = wsp;
    try
    {
    spArr.arraySimplePoint[125] = wsp;
    }
    catch (IndexOutOfRangeException exc)
    {
    Console.WriteLine(exc);
    }
    
    try
    {
    wsp.arraySimplePoint[7] = wsp;
    }
    catch (NullReferenceException exc)
    {
    Console.WriteLine(exc);
    }
    
    try
    { 
    wsp0 = spArr.arraySimplePoint[125];
    }
    catch (IndexOutOfRangeException exc)
    {
    Console.WriteLine(exc);
    }
    
    wsp0 = spArr.arraySimplePoint[5];
    
    MyPoint mpArr = new MyPoint(10);
    MyPoint wmp = new MyPoint(3.14F, 3.14F);
    MyPoint wmp0;
    
    mpArr[3] = wmp;
    mpArr[125] = wmp;
    wmp[7] = wmp;
    wmp0 = mpArr[125];
    wmp0 = mpArr[5];
    // В качестве индексатора используются строковые выражения.
    wmp0 = mpArr["5"];
    // В том числе использующие конкатенацию строк.
    wmp0 = mpArr["1"+"2"+"5"];
    }
    }
    }

    explicit и implicit. Преобразования явные и неявные

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

    Логика построения явных и неявных преобразователей достаточно проста. Программист самостоятельно принимает решение относительно того:

  • каковым должен быть алгоритм преобразования;
  • будет ли этот алгоритм выполняться неявно или необходимо будет явным образом указывать на соответствующее преобразование.
  • Ниже рассматривается пример, содержащий объявления классов Point2D и Point3D. В классах предусмотрены алгоритмы преобразования значений от одного типа к другому, которые активизируются при выполнении операций присвоения:

    using System;
    
    // Объявления классов.
    class Point3D
    {
    public int x,y,z;
    
    public Point3D()
    {
    	x = 0;
    	y = 0;
    	z = 0;
    }
    
    public Point3D(int xKey, int yKey, int zKey)
    {
    	x = xKey;
    	y = yKey;
    	z = zKey;
    }
    
    // Операторная функция, в которой реализуется алгоритм преобразования
    // значения типа Point2D в значение типа Point3D. Это преобразование
    // осуществляется НЕЯВНО.
    public static implicit operator Point3D(Point2D p2d)
    {
        Point3D p3d = new Point3D();
        p3d.x = p2d.x;
        p3d.y = p2d.y;
        p3d.z = 0;
        return p3d;
    }
    } 	
    
    class Point2D
    {
    public int x,y;
    
    public Point2D()
    {
     x = 0;
     y = 0;
    }
    
    public Point2D(int xKey, int yKey)
    {
     x = xKey;
     y = yKey;
    }
    
    // Операторная функция, в которой реализуется алгоритм преобразования
    // значения типа Point3D в значение типа Point2D. Это преобразование
    // осуществляется с ЯВНЫМ указанием необходимости преобразования.
    // Принятие решения относительно присутствия в объявлении ключевого
    // слова explicit вместо implicit оправдывается тем, что это
    // преобразование сопровождается потерей информации. Существует мнение,
    // что об этом обстоятельстве программисту следует напоминать всякий раз,
    // когда он в программном коде собирается применить данное преобразование.
    public static explicit operator Point2D(Point3D p3d)
    {
    	Point2D p2d = new Point2D();
    	p2d.x = p3d.x;
    	p2d.y = p3d.y;
    	return p2d;
    }
    
    } 	
    // Тестовый класс. Здесь все происходит.
    class TestClass
    {
     static void Main(string[] args)
     {
      Point2D p2d = new Point2D(125,125);
      Point3D p3d; // Сейчас это только ссылка!
      // Этой ссылке присваивается значение в результате
      // НЕЯВНОГО преобразования значения типа Point2D к типу Point3D
     p3d = p2d;
    
     // Изменили значения полей объекта.  
     p3d.x = p3d.x*2; 
     p3d.y = p3d.y*2; 
     p3d.z = 125; // Главное – появилась новая информация,
      // которая будет потеряна в случае присвоения значения типа Point3D
     // значению типа Point2D.
      // Ключевое слово explicit в объявлении соответствующего
      // метода преобразования вынуждает программиста подтверждать,
      // что он в курсе возможных последствий этого преобразования.
      p2d = (Point2D)p3d;
    }
    }
    Вернуться к учебному плану