В пособии также рассматриваются анонимные методы и процедурные ссылки.
Словарь терминов
- generics - дженерики, генерики, параметризованные классы, шаблоны, обобщения;
- Типы данных: string – строка, record – запись;
- implicit conversion - неявное приведение (типа);
- constraint – ограничение;
- actual types – фактические типы
- generic class – обобщённый класс;
- generic parameter – обобщённый параметр;
- сast – приведение типа;
- anonymous routines - анонимные методы;
- routine references - процедурные ссылки;
- ordinal type – порядковый тип (данных);
- interface type – интерфейсный тип (данных);
- class type – классовый тип, иногда просто класс (так как класс сам по себе является типом);
- class method – классовый метод; метод класса; class procedure;
- comparer – компаратор;
- unit – модуль, юнит;
- instanciate – создание экземпляра объекта;
- ordinal type – порядковый тип (данных)
VIII. Обобщённые методы
Мы рассмотрели много возможностей, которые предоставляют дженерики, применительно к типам, определённым разработчиком. Настало время перейти к обобщённым методам.
Эту часть часто объясняют во многих презентациях и шаблонах по использованию дженериков в других языках. Но я предпочёл начать с обобщённых типов, которые используются чаще всего.
VIII-A. Обобщённая функция Min
Для того, чтобы лучше представить эту концепцию, мы напишем метод для класса TArrayUtils.Min<T>, который найдёт и вернёт самый маленький элемент массива. Для этого нам, конечно же, понадобится компаратор типа IComparer<T>.
Как и название обобщённого типа, название обобщённого метода должно сопровождаться обобщённым параметром, расположенным между угловыми скобками. Здесь, обобщённый тип является типом элемента массива.
type TArrayUtils = class public class function Min<T>(const Items: array of T; const Comparer: IComparer<T>): T; static; end; |
К сожалению, не представляется возможным объявить глобальную процедуру с обобщёнными параметрами. Вероятно, причина этого ограничения связана с параллелизмом в синтаксисе Delphi.NET, чтобы уменьшить усилия по разработке и поддержанию. |
Чтобы компенсировать это ограничение, мы будем использовать классовые методы. Лучше, чтобы они были объявлены с ключевым словом static. У него нет ничего общего с омонимичным ключевым словом в C++. Речь здесь идёт о статическом связывании. Это говорит о том, что в таком методе нет Self, и тогда вызываются виртуальные методы класса, или виртуальные конструкторы должны быть "виртуализированы".(примечание переводчика: я не понял, что здесь имелось в виду: «This is to say, in such a method, there is no Self, and then calls to virtual class methods, or to virtual constructors, are to "virtualised".»). Другими словами, это как если бы они больше не были виртуальными.
|
В отличие от других языков, параметр не обязательно должен использовать обобщённый тип. Таким образом, можно написать метод Dummy<T>(Int: Integer): Integer, который не имеет формального параметра, чей тип зависел бы от обобщённого параметра T. В C++, например, такое не получилось бы даже скомпилировать. |
Реализация этого метода очень похожа на классы. Вы должны повторить угловые скобки для обобщённого типа, но не для возможных ограничений. Наш метод Min будет выглядеть следующим образом:
class function TArrayUtils.Min<T>(const Items: array of T; const Comparer: IComparer<T>): T; var I: Integer; begin if Length(Items) = 0 then raise Exception.Create('No items in the array'); Result := Items[Low(Items)]; for I := Low(Items)+1 to High(Items) do if Comparer.Compare(Items[I], Result) < 0 then Result := Items[I]; end; |
Ничего исключительного, как Вы можете видеть. ;-)
VIII-B. Переопределения и ограничения
Давайте воспользуемся преимуществом этого прекрасного примера, чтобы пересмотреть наши ограничения и сделать перегруженную версию для тех типов элементов, которые поддерживают интерфейс <T> (он объявлен в System.pas).
Перед этим, давайте добавим ещё одно переопределение, которое будет принимать процедурную ссылку типа TComparison<T>. Помните, мы легко можем «преобразовать» функцию обратного вызова типа TComparison<T> в интерфейс IComparer<T>, вызвав метод TComparer<T>.Construct.
Вы можете увидеть использование метода CompareTo на параметре Left. Такой вызов допускается только из-за наличия ограничения.
type TArrayUtils = class public class function Min<T>(const Items: array of T; const Comparer: IComparer<T>): T; overload; static; class function Min<T>(const Items: array of T; const Comparison: TComparison<T>): T; overload; static; class function Min<T: IComparable<T>>( const Items: array of T): T; overload; static; end; class function TArrayUtils.Min<T>(const Items: array of T; const Comparison: TComparison<T>): T; begin Result := Min<T>(Items, TComparer<T>.Construct(Comparison)); end; class function TArrayUtils.Min<T>(const Items: array of T): T; var Comparison: TComparison<T>; begin Comparison := function(const Left, Right: T): Integer begin Result := Left.CompareTo(Right); end; Result := Min<T>(Items, Comparison); end; |
Обратите внимание на вызов Min<T>: указание актуального типа между угловыми скобками является обязательным даже там. Это не относится к другим языкам, таким как C++. |
Теперь мы хотим предоставить четвёртый перегруженный метод, снова только с одним параметром Items, но на этот раз с параметром T без ограничений. Эта версия должна будет использовать TComparer<T>.Default.
Но это невозможно! На самом деле, несмотря на то, что ограничения на типы будут измены, параметры (аргументы) остаются теми же самыми. Поэтому, такие перегруженные методы будут совершенно двусмысленны! Таким образом, следующее объявление приведёт к ошибке компилятора:
type TArrayUtils = class public class function Min<T>(const Items: array of T; const Comparer: IComparer<T>): T; overload; static; class function Min<T>(const Items: array of T; const Comparison: TComparison<T>): T; overload; static; class function Min<T: IComparable<T>>( const Items: array of T): T; overload; static; class function Min<T>( const Items: array of T): T; overload; static; // ошибка компилятора end; |
Следовательно, мы должны выбрать: отказаться от первого или второго, или же использовать другое имя. И до тех пор, пока базовые типы, такие как Integer не начнут поддерживать интерфейс IComparable<T>, Вам могут понадобиться оба, остаётся выбрать использование другого имени;-)
type TArrayUtils = class public class function Min<T>(const Items: array of T; const Comparer: IComparer<T>): T; overload; static; class function Min<T>(const Items: array of T; const Comparison: TComparison<T>): T; overload; static; class function Min<T: IComparable<T>>( const Items: array of T): T; overload; static; class function MinDefault<T>( const Items: array of T): T; static; end; class function TArrayUtils.MinDefault<T>(const Items: array of T): T; begin Result := Min<T>(Items, IComparer<T>(TComparer<T>.Default)); end; |
Почему я явно привожу Default к типу IComparer<T>, несмотря на то, что он уже явно означает IComparer<T>? Потому что процедурные ссылки и перегруженные методы до сих пор не очень хорошо работают друг с другом, и компилятор, кажется, начинает путаться от такого смешения. Без приведения типов, компиляция не удаётся...
VIII-C. Некоторые дополнения для TList<T>
Несмотря на то, что TList<T> является очень хорошим нововведением, оно никогда не сможет предоставить более практичных методов.
Вот пример реализации .NET метода FindAll для TList <T>. Этот метод направлен на получение подсписка с помощью функции предиката. Под функцией предиката здесь подразумевается процедура обратного вызова, которая принимает элемент списка, и возвращает True, если он должен быть выбран, и False в обратном случае. Мы определяем процедурную ссылку типа TPredicate<T> следующим образом:
unit Generics.CollectionsEx; interface uses Generics.Collections; type TPredicate<T> = reference to function(const Value: T): Boolean; |
К сожалению, невозможно написать class helper для обобщённого класса, поэтому мы напишем метод класса FindAll<T> который реализует это. Поскольку мы лишены возможности использовать class helper, мы постараемся хотя бы использовать его преимущество в том, чтобы реализовать более общее решение, работающее с энумератором или перечисляемым типом. (примечание: в оригинале было «enumerator or enumerable»)
type TListEx = class public class procedure FindAll<T>(Source: TEnumerator<T>; Dest: TList<T>; const Predicate: TPredicate<T>); overload; static; class procedure FindAll<T>(Source: TEnumerable<T>; Dest: TList<T>; const Predicate: TPredicate<T>); overload; static; end; implementation class procedure TListEx.FindAll<T>(Source: TEnumerator<T>; Dest: TList<T>; const Predicate: TPredicate<T>); begin while Source.MoveNext do begin if Predicate(Source.Current) then Dest.Add(Source.Current); end; end; class procedure TListEx.FindAll<T>(Source: TEnumerable<T>; Dest: TList<T>; const Predicate: TPredicate<T>); begin FindAll<T>(Source.GetEnumerator, Dest, Predicate); end; end. |
Можно использовать этот метод следующим образом:
Source := TList<Integer>.Create; try Source.AddRange([2, -9, -5, 50, 4, -3, 7]); Dest := TList<Integer>.Create; try TListEx.FindAll<Integer>(Source, Dest, TPredicate<Integer>( function(const Value: Integer): Boolean begin Result := Value > 0; end)); for Value in Dest do WriteLn(Value); finally Dest.Free; end; finally Source.Free; end; |
Опять же, приведение типов необходимо из-за переопределений. Это довольно печально, но пока это так. Если вы предпочитаете обходится без приведений типов, используйте разные названия или постарайтесь избавится от одного из двух переопределений. |
Задачу, дополнения этого класса другими методами наподобие FindAll я оставлю вам. :-)
- Оригинальный текст: Sébastien Doeraene. Copyright ©2008-2009.
- Перевод: Алексей Тимохин. Copyright ©2009.
- Дата публикации: 13 ноября 2008 года
- Дата перевода: июль-сентябрь 2009 года.
Публикации по теме
- Подробное оглавление
- Примечания к “переводу”
- I. Введение
- II. Повседневное использование на примере TList<T>
- III. Создание обобщённого класса.
- IV. Создание обобщённой записи
- V. Ограничения обобщённых типов
- VI. Использование в качестве параметра более чем одного типа
- VII. Другие типы дженериков
- VIII. Обобщённые методы
- IX. RTTI и дженерики
- Завершающе слово и ссылка на скачивание исходников (не переведено).
Спасибо за работу Алексей.
ОтветитьУдалитьВ "Публикации по теме" ссылка на 3-ю часть ведет на 2-ую.
Добрый день
ОтветитьУдалитьВы писали:
"Потому что процедурные ссылки и перегруженные методы до сих пор не очень хорошо работают друг с другом, и компилятор, кажется, начинает путаться от такого смешения. Без приведения типов, компиляция не удаётся..."
У меня есть вопросы по этому поводу. Если не трудно, то свяжитесь со мной по ICQ: 334-403-320
(связался бы сам с вами, да контактов ваших нигде не нашел)
Добрый день.
ОтветитьУдалитьМои контактные данные указаны в панели в правой части блога. =)