Чтение онлайн

ЖАНРЫ

C# для профессионалов. Том II

Ватсон Карли

Шрифт:

public static implicite operator MyDest(MySource Source) {

 // код для выполнения преобразования. Должен возвращать экземпляр MyDest

}

Преобразование типа данных определяется как статический член класса

MyDest
или класса
MySource
. Оно должно также объявляться любо как
implicit
, либо как
explicit
. Если преобразование объявлено как
implicit
, то оно используется неявно:

MySource Source = new MySource;

MyDest Dest = MySource;

Если

преобразование объявлено как explicit, то оно может использоваться только явным образом:

MySource Source = new MySource;

MyDest Dest = (MyDest)MySource;

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

Так же как и в C++, если компилятор C# встречается с запросом преобразования между типами данных, для которых не существует прямого преобразования типов, он будет стараться найти "лучший" способ, используя доступные методы преобразования типов. Существуют те же вопросы, что и в C++, в отношении интуитивной ясности преобразований типов данных, а также в том, что различные пути получения преобразования не создают несовместимых результатов.

C# не позволяет определить преобразования типов данных между классами, которые являются производными друг друга. Такие преобразования уже доступны — неявно из производного класса в базовый класс и явно из базового класса в производный.

Отметим, что если попробовать выполнить преобразование ссылки базового класса в ссылку производного класса, и при этом рассматриваемый объект не является экземпляром производного класса (или какого-нибудь производного из него), то будет порождаться (генерироваться) исключение. В C++ нетрудно преобразовать указатель на объект в "неправильный" класс объектов. Это просто невозможно в C# с помощью ссылок. По этой причине преобразование типов в C# считается более безопасным, чем в C++.

// пусть MyDerivedClass получен из MyBaseClass

MyBaseClass MyBase = new MyBaseClass;

MyDerivedClass MyDerived = (MyDerivedClass) MyBase; // это приведет

 // к порождению исключения

Если нежелательно преобразовывать что-то в производный класс, но нежелательно также, чтобы генерировалось исключение, можно применить ключевое слово

as
. При использовании as, если преобразование отказывает, будет возвращаться
null
.

// пусть MyDerivedClass получен из MyBaseClass

MyBaseClass MyBase = new MyBaseClass;

MyDerivedClass MyDerived as (MyDerivedClass)MyBase; // это

// возвратит null

Массивы

Массивы являются одной из областей, в которой внешнее сходство в синтаксисе между C++ и C# скрывает то, что реально происходящее "за сценой" существенно различается в этих двух языках. В C++ массив является по сути множеством переменных, упакованных вместе в памяти и доступных через указатель. В C#, с другой стороны, массив является экземпляром базового класса

System.Array
, и поэтому выступает полноценным объектом, хранящимся в куче под управлением сборщика мусора. Для доступа к методам этого класса C# использует синтаксис
типа C++ способом, который создает иллюзию доступа к массиву. Недостаток этого подхода состоит в том, что накладные расходы для массивов больше, чем в C++, но преимуществом является то, что массивы C# более гибкие и при этом проще кодируются. В качестве примера: все массивы C# имеют свойство
Length
, которое задает число элементов массива, не требуя тем самым хранить его отдельно. К тому же массивы C# значительно безопаснее в использовании — так, проверка границ индекса выполняется автоматически.

В C# возможно сделать простой массив без накладных расходов класса

System.Array
, но для этого понадобится использовать указатели и ненадежные блоки кода.

Одномерные массивы

Для одномерных массивов (терминология C#: массивы ранга 1) синтаксис доступа в обоих языках идентичен — с квадратными скобками, используемыми для индикации элементов массива. Массивы начинаются с нулевого индекса в обоих языках.

Например, чтобы умножить каждый элемент массива

float
на 2:

// массив, объявлен как массив float

// этот код работает в C++ и C# без каких-либо изменений

for (int i = 0; i < 10; i++) Array[i] = 2.0f;

Однако, как упоминалось ранее, массивы C# поддерживают свойство

Length
, которое используется для определения числа элементов в массиве.

// массив, объявлен как массив float

// этот код компилируется только в C#

for (int i = 0; i < Array.Length; i++) Array[i] *= 2.0f;

В C# можно также использовать инструкцию

foreach
для доступа к элементам массива, что рассматривалось ранее.

Синтаксис объявления массивов в C# слегка отличается, так как массивы C# всегда объявляются как ссылочные объекты.

double [] Array; // простое объявление ссылки без реального

// создания экземпляра массива

Array = new double[10]; // реально создается экземпляр объекта

// System.Array и задается размер 10.

Или, объединяя эти инструкции, запишем:

double [] array = new double[10];

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

Ближайший эквивалент в C++ приведенного выше определения будет выглядеть так:

double *pArray = new double[10];

Эта инструкция C++ действительно дает достаточно близкую аналогию, так как обе версии C++ и C# размещаются в куче. Отметим, что версия C++ является просто областью памяти, которая содержит 10

double
, в то время как версия C# создает экземпляр полноценного объекта. Более простая стековая версия C++:

doublе pArray[10];

не имеет аналога в C#, который использует реальный массив C#, хотя инструкция C#

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

Поделиться с друзьями: