PascalABC.NET

Ограничения на параметры обобщенных подпрограмм и классов

По умолчанию с переменными, имеющими тип параметра обобщенного класса или подпрограммы, внутри методов обобщённых классов и обобщенных подпрограмм можно делать лишь ограниченный набор действий: присваивать и сравнивать на равенство (отметим, что в NET сравнение на равенство внутри обобщений запрещено!).

Например, данный код будет работать:

function Eq<T>(a,b: T): boolean;
begin
  Result := a = b;
end;

Можно также использовать присваивание переменной, имеющей тип параметра обобщенного класса или подпрограммы, значение по умолчанию, используя конструкцию default(T) - значение по умолчанию для типа T (nil для ссылочных типов и нулевое значение для размерных типов):

procedure Def<T>(var a: T);
begin
  a := default(T);
end;

Однако, данный код

function Sum<T>(a,b: T): T;
begin
  Result := a + b;
end;

вызовет ошибку компиляции до инстанцирования (создания экземпляра с конкретным типом). Такое поведение в .NET кардинально отличается от шаблонов в C++, где в коде шаблона можно использовать любые операции с шаблонными параметрами, и ошибка может произойти только в момент инстанцирования с конкретным типом.

Чтобы разрешить использование некоторых действий с переменными, имеющими тип параметра обобщенного класса или подпрограммы, используются ограничения на обобщенные параметры, задаваемые в секции where после заголовка подпрограммы или класса:

type
  MyPair<T> = class
    where T: System.ICloneable;
  private
   
x,y: T;
  public
    constructor
(x,y: T);
    begin
      Self.x := x;
      Self.y := y;
    end;
    function Clone: MyPair;
    begin
      Result := new MyPair<T>(x.Clone,y.Clone);
    end;
  end;

В секции where через запятую перечисляются следующие ограничения:
На 1 месте: слово class или слово record или имя класса-предка.
На 2 месте: список реализуемых интерфейсов через запятую.
На 3 месте: слово constructor, указывающее, что данный тип должен иметь конструктор по умолчанию.

При этом каждое из мест, кроме одного, может быть пустым.

Для каждого типа-параметра может быть своя секция where, каждая секция where завершается точкой с запятой.

Пример. Обобщенная функция поиска минимального элемента в массиве. Элементы должны реализовывать интерфейс IComparable<T>.

function MinElem<T>(a: array of T): T;
  where T: IComparable<T>;
begin
  var min := a[0];
  for var i := 1 to a.High do
    if a[i].CompareTo(min)<0 then
      min := a[i];
  Result := min;
end;

К сожалению, нет возможности использовать запись a[i]<min, поскольку операции не входят в интерфейсы.