Использование универсальных контейнеров в Delphi XE - всегда? - PullRequest
11 голосов
/ 15 марта 2011

Универсальные контейнеры могут сэкономить время при наличии предмета и строго типизированного списка этих предметов.Это экономит повторяющееся кодирование создания нового класса с, возможно, внутренней переменной TList и типизированных методов типа Add / Delete, среди других преимуществ (таких как все новые функциональные возможности, предоставляемые классами универсальных контейнеров.)

Однакорекомендуется всегда использовать универсальные контейнеры для строго типизированных списков в будущем?Каковы конкретные недостатки этого?(Если не беспокоиться о обратной совместимости кода.) Вчера я писал серверное приложение, у меня был список элементов, которые я создал «по-старому» и собирался заменить его на общий список, но решил сохранить его скудным, нов основном по привычке.(Должны ли мы отказаться от привычки и начать новую, всегда используя дженерики?)

Ответы [ 7 ]

12 голосов
/ 15 марта 2011

В Delphi XE нет причин не использовать универсальные контейнеры.

Переключение со старого метода на приведение даст вам:

  • чище, безопаснее по типу, меньшеподверженный ошибкам код,
  • счетчики, для циклов in,
  • то же самое лучшие характеристики производительности.
10 голосов
/ 16 марта 2011

Это было вызвано ответом Делтика, я хотел привести контрпример, доказывающий, что вы можете использовать дженерики для кормления животных.(т.е.: Полиморфный универсальный список)

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

TBaseList = class
  // Some code to actually make this a list
end

TSpecificList = class(TBaseList)
  // Code that reintroduces the Add and GetItem routines to turn TSpecificList
  // into a type-safe list of a different type, compatible with the TBaseList
end

Это не работает с дженериками, потому что обычно у вас будет это:

TDogList = TList<TDog>
end

TCatList = TList<TCat>
end

... и единственным "общим предком" для обоих списков является TObject - совсем не полезно.Но мы можем определить новый тип универсального списка, который принимает два аргумента класса: TAnimal и TSpecificAnimal, генерируя безопасный для типа список TSpecificAnimal, совместимый с универсальным списком TAnimal.Вот базовое определение типа:

TCompatibleList<T1:class;T2:class> = class(TObjectList<T1>)
private
  function GetItem(i: Integer): T2;
public
  procedure Add(A:T2);
  property Item[i:Integer]:T2 read GetItem;default;
end;

Используя это, мы можем сделать:

TAnimal = class; 
TDog = class(TAnimal); 
TCat = class(TAnimal);

TDogList = TCompatibleList<TAnimal, TDog>;
TCatList = TCompatibleList<TAnimal, TCat>;

Таким образом, TDogList и TCatList фактически наследуются от TObjectList<TAnimal>, поэтому у нас теперь есть полиморфный универсальный универсальныйlist!

Вот полное консольное приложение, которое демонстрирует эту концепцию в действии.И этот класс теперь входит в мою ClassLibrary для дальнейшего использования!

program Project23;

{$APPTYPE CONSOLE}

uses
  SysUtils, Generics.Collections;

type

  TAnimal = class
  end;

  TDog = class(TAnimal)
  end;

  TCat = class(TAnimal)
  end;

  TCompatibleList<T1:class;T2:class> = class(TObjectList<T1>)
  private
    function GetItem(i: Integer): T2;
  public
    procedure Add(A:T2);
    property Item[i:Integer]:T2 read GetItem;default;
  end;

{ TX<T1, T2> }

procedure TCompatibleList<T1, T2>.Add(A: T2);
begin
  inherited Add(T1(TObject(A)));
end;

function TCompatibleList<T1, T2>.GetItem(i: Integer): T2;
begin
  Result := T2(TObject(inherited Items[i]));
end;

procedure FeedTheAnimals(L: TObjectList<TAnimal>);
var A: TAnimal;
begin
  for A in L do
    Writeln('Feeding a ' + A.ClassName);
end;

var Dogs: TCompatibleList<TAnimal, TDog>;
    Cats: TCompatibleList<TAnimal, TCat>;
    Mixed: TObjectList<TAnimal>;

begin
  try
    // Feed some dogs
    Dogs := TCompatibleList<TAnimal, TDog>.Create;
    try
      Dogs.Add(TDog.Create);
      FeedTheAnimals(Dogs);
    finally Dogs.Free;
    end;
    // Feed some cats
    Cats := TCompatibleList<TAnimal, TCat>.Create;
    try
      Cats.Add(TCat.Create);
      FeedTheAnimals(Cats);
    finally Cats.Free;
    end;
    // Feed a mixed lot
    Mixed := TObjectList<TAnimal>.Create;
    try
      Mixed.Add(TDog.Create);
      Mixed.Add(TCat.Create);
      FeedTheAnimals(Mixed);
    finally Mixed.Free;
    end;
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
4 голосов
/ 15 марта 2011

Должны ли мы сломать привычку и начать новую, всегда используя дженерики? ДА

3 голосов
/ 15 марта 2011

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

2 голосов
/ 16 марта 2011

В духе ответа Космина, по сути, ответа на ответ Делтика, вот как исправить код Делтика:

type
  TAnimal = class
  end;

  TDog = class(TAnimal)
  end;

  TAnimalList<T:TAnimal> = class(TList<T>)
    procedure Feed;
  end;
  TDogList = TAnimalList<TDog>;

Теперь вы можете написать:

var
  Dogs: TDogList;
...
  Dogs.Feed;
1 голос
/ 16 марта 2011

Если вам нужны полиморфные списки, тогда дженерики - это помеха, а не помощь.Это даже не компилируется, например, потому что вы не можете использовать TDogList там, где требуется TAnimalList:

  uses
    Generics.Collections;

  type
    TAnimal = class
    end;

    TDog = class(TAnimal)
    end;

    TAnimalList = TList<TAnimal>;
    TDogList = TList<TDog>;


  procedure FeedTheAnimals(const aList: TAnimalList);
  begin
    // Blah blah blah
  end;


  var
    dogs: TDogList;
  begin
    dogs := TDogList.Create;
    try
      FeedTheAnimals(dogs);

    finally
      dogs.Free;
    end;
  end;

Причины этого достаточно ясны и легко объяснены, но это столь же противоречит интуитивно.

Мое собственное мнение состоит в том, что вы можете сэкономить несколько секунд или минут (если вы медлительный машинистка), используя универсальный тип вместо того, чтобы использовать безопасный контейнер типа, более конкретный и соответствующий вашим потребностям, но вы вполне можетев конечном итоге вы потратите больше времени на решение проблем и ограничений Generics в будущем, чем сэкономили, используя их для начала (и по определению, если вы до сих пор не использовали универсальные контейнеры, вы не знаете, что это за проблемы)./ ограничения могут быть до тех пор, пока вы не столкнетесь с ними). ​​

Если мне нужен TAnimalList, скорее всего, мне нужны или могут быть полезны дополнительные специфичные для TAnimal методы в этом классе списка, которые я хотел бы наследовать в TDogListкоторый, в свою очередь, может ввести дополнительных конкретных членов, имеющих отношение кк предметам TDog.

(Животные и собаки, конечно, используются только в иллюстративных целях.На самом деле я сейчас не работаю над ветеринарным кодом - LOL)

Проблема в том, что вы не всегда знаете это с самого начала.

Принципы защитного программирования предполагают (для меня, ммм)эта картина в углу ради экономии времени, скорее всего, в будущем обойдется очень дорого.А если это не так, дополнительная «стоимость» отказа от первоначальной экономии сама по себе ничтожна.

Кроме того, ваш код более доступен для пользователей более старых версий Delphi, если вы склонны быть настолько щедрыми.

:)

1 голос
/ 15 марта 2011

Вы писали о обратной совместимости ... это мое самое большое беспокойство, если (как и я) вы пишете библиотеки, которые должны лучше компилироваться с большинством распространенных версий Delphi.

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

В будущем назначении вам, возможно, придется поддерживать какой-то старый код, без возможности обновления до более новой версии Delphi.(нет денег на миграцию и просмотр 1 000 000 строк кода).В этом случае вы могли бы пропустить свои библиотеки только для XE с блестящими списками на основе универсальных шаблонов ...

Но для 100% " private " приложения, если вы уверены, что выникогда не придется поддерживать старый код Delphi, я не вижу причин, чтобы не использовать дженерики.Единственное, что меня беспокоит, - это проблема дублированного кода (по словам Мейсона): кэш ЦП может быть заполнен ненужным кодом, поэтому скорость выполнения может пострадать.Но в реальном приложении, я думаю, вы не увидите никакой разницы.

Примечание : я только что добавил новых функций в свою оболочку TDynArray .Я попытался имитировать пример кода из EMB docwiki .Таким образом, у вас могут быть общие функции, как в старых добрых версиях Delphi ... Конечно, общие шаблоны лучше работают с классами, но с некоторыми массивами и записями они просто потрясающие!

...