ЯЗЫК ПРОГРАММИРОВАНИЯ С# 2005 И ПЛАТФОРМА .NET 2.0. 3-е издание - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
Исходный код. Проект SimpleIndexer размещен в подкаталоге, соответствующем главе 9.
Вариации индексатора для типа Garage
В своем текущем виде тип Gаrage определяет индексатор, который позволяет вызывающей стороне идентифицировать внутренние элементы, используя число-вое значение. Но это не является непременным требованием метода индексатора. Предположим, что объекты Car содержатся в System.Collections.Specialized. ListDictionary, а не в ArrayList. Поскольку типы ListDictionary позволяют доступ к содержащимся типам с помощью ключевых маркеров (таких как, например, строки), можно создать новый индексатор Garage, подобный показанному ниже.
public class Garage: IEnumerable {
private ListDictionary carDictionary = new ListDictionarу();
// Этот индексатор возвращает соответствующий тип Car
// на основе строкового индекса.
public Car this[string name] {
get { return (Car)carDictionary[name]; }
set { carDictionary[name] = value; }
}
public int Length { get { return carDictionary.Count; } }
public IEnumerator GetEnumerator() { return carDictionary.GetEnumerator(); }
}
Вызывающая сторона теперь может взаимодействовать с машинами внутри так, как показано ниже,
public class Program {
static void Main(string[] args) {
Console:WriteLine("***** Забавы с индексаторами *****n");
Garage carLot = new Garage();
// Добавление именованных машин в гараж.
carLot["FeeFee"] = new Car("FeeFee", 200, 0);
carLot["Clunker"] = new Car("Clunker", 90, 0);
carLot["Zippy"] = new Car("Zippy", 30, 0);
// Доступ к Zippy.
Car zippy = carLot["Zippy"];
Console.WriteLine("{0} едет со скоростью {1} км/ч", zippy.PetName, zippy.CurrSpeed);
Console.ReadLine();
}
}
Индексаторы могут быть и перегруженными. Так, чтобы позволить вызывающей стороне доступ к внутренним элементам посредством числового индекса или строковых значений, вы можете определить множество индексаторов для одного типа.
Исходный код. Проект StringIndexer размещен в подкаталоге, соответствующем главе 9.
Внутреннее представление индексаторов типов
Мы рассмотрели примеры метода индексатора в C#, и пришло время выяснить, как представляются индексаторы в терминах CIL. Если открыть числовой индексатор типа Garage, то будет видно, что компилятор C# создает свойство Item, которое сводится к подходящей паре методов get/set.
property instance class SimpleIndexer.Car Item(int32) {
.get instance class SimpleIndexer.Car SimpleIndexer.Garage::get_Item(int32)
.set instance void SimpleIndexer.Garage::set_Item(int32, class SimpleIndexer.Car)
} // end of property Garage::Item
Методы get_Item() и set_Item() будут реализованы аналогично любому другому свойству .NET, например:
method public hidebysig specialname instance сlass SimpleIndexer.Car get_Item(int32 pos) cil managed {
Code size 22 (0x16)
.maxstack 2
.locals init ([0] class SimpleIndexer.Car CSS1$0000)
IL_0000: ldarg.0
IL_0001: ldfld class [mscorlib] System.Collections.ArrayList SimpleIndexer.Garage::carArray
IL_0006: ldarg.1
IL_0007: callvirt instance object [mscorlib] Sysftem.Collections.ArrayList::get_Item(int32)
IL_000c: castclass SimpleIndexer.Car
IL_0011: stloc.0
IL_0012: br.s IL_0014
IL_0014: ldloc.0
IL_0015: ret
} // end of method Garage::get_Item
Заключительные замечания об индексаторах
Чтобы получить настоящую экзотику, вы можете создать индексатор, который имеет множество параметров. Предположим, что у нас есть пользовательская кол-лекция, которая хранит элементы в двумерном массиве. В этом случае вы можете создать метод индексатора, доказанный ниже.
public class SameContainer {
private int[,] my2DinArray = new int[10, 10];
public int this[int row, int column] {/* прочитать или установить значение 2D-массива * /}
}
В заключение следует заметить, что индексаторы могут определяться и для типа интерфейса .NET, что обеспечивает реализующим интерфейс типам возможность его настройки. Вот пример такого интерфейса.
public interface IEstablishSubObjects {
// Этот интерфейс определяет индексатор, возвращающий
// строки на основе числового индекса.
string this[int index] {get; set;}
}
Пожалуй, об индексаторах C# уже сказано достаточно. Перейдем к рассмотрению еще одного подхода, используемого в некоторых (но не во всех) языках программирования .NET: это перегрузка операций.
Перегрузка операций
В C#, как и в любом другом языке программирования, есть свой ограниченный набор лексем, используемых для выполнения базовых операций со встроенными типами. Так, вы знаете, что операция + применима к двум целым числам и в результате дает их сумму.
// Операция + с целыми числами.
int а = 100;
int b = 240;
int с = а + b; // с теперь равно 340
Снова заметим, что это не новость, но вы, наверное, заметили и то, что одна и та же операция + может применяться к большинству встроенных типов данных C#. Рассмотрите, например, следующий фрагмент программного кода.
// Операция + со строками.
string s1 = "Hello";
string s2 = " world!";
string s3 = s1 + s2; // s3 теперь равно "Hello world!"
По сути, операция + функционирует уникальным образом в зависимости от поставляемых типов данных (в данном случае это строки или целые числа). Когда операция + применяется к числовым типам, результатом является сумма операндов, а когда операция + применяется к строковым типам, результатом будет конкатенация строк.
Язык C# обеспечивает возможность построения пользовательских классов и структур, которые будут по-своему отвечать на один и тот же набор базовых лексем (таких, как операция +). При этом следует заметить, что можно "перегружать" не все встроенные операции C#. В табл. 9.1 указаны возможности перегрузки базовых операций.
Таблица 9.1. Возможности перегрузки операций
Операции C# Возможность перегрузки +, -, !, ~, ++, --, true, false Эти унарные операции допускают перегрузку +, -, *, /, %, &, |, ^, ‹‹, ›› Эти бинарные операции допускают перегрузку ==, !=, ‹, ›, ‹=, ›= Операции сравнения допускают перегрузку. В C# требуется, чтобы перегрузка "родственных" операций (т.е. ‹ и ›, ‹= и ›=, == и !=) выполнялась одновременно [] Операция [] не допускает перегрузку. Но, как было показано выше, аналогичные перегрузке возможности обеспечивает конструкция индексатора () Операция () не допускает перегрузку. Но, как будет показано ниже, аналогичные перегрузке возможности обеспечивают пользовательские методы преобразования +=, -=, *=, /=, %=, &=, |=, ^=, ‹‹=, ››= Операторные сокращения с присваиванием сами по себе не допускают перегрузку, однако для них перегруженная форма получается автоматически в результате перегрузки соответствующей бинарной операцииПерегрузка бинарных операций
Чтобы проиллюстрировать процесс перегрузки бинарных операций, расcмо-трим следующую простую структуру Point (точка).
// Самая обычная структура C#.
public struct Point {
private int x, y;
public Point(int xPos, int yPos) {
x = xPos; у = yPos;
}
public override string ToString() {
return string.Format("[{0}, {1}]", this.x, this.у);
}
}
Можно, скажем, рассмотреть сложение типов Point. Можно также вычесть один тип Point из другого. Например, вы можете записать, следующий программный код.
// Сложение и вычитание двух точек.
static vоid Main(string [] args) {
Console.WriteLine("*** Забавы с перегруженными операциями ***n");
// Создание двух точек.
Point ptOne = new Point(100, 100);
Point ptTwo = new Point (40, 40);
Console.WriteLine("ptOne = {0}", ptOne);
Console.WriteLine("ptTwo = {0}", ptTwo);
// Сложение точек в одну большую точку?
Console.WriteLine("ptOne + ptTwo: {0} ", ptOne + ptTwo);
// Вычитание одной точки из другой дает меньшую точку?
Console.WriteLine("ptOne – ptTwo: {0} ", ptOne – ptTwo);
Console.ReadLine();
}
Чтобы позволить пользовательскому типу по-своему отвечать на встроенные операции, в C# предлагается ключевое слово operator, которое можно использовать только со статическими методами. Перегруженной бинарной операции (такой, как + или -) на вход подаются два аргумента, которые имеют тип определяющего класса (в данном примере это Point), как показывает следующий программный код.