Delphi: типы, отличные от Integer, для индексации элементов TStringList

Arrays можно индексировать с помощью пользовательских перечислимых типов. Например:

type
  TIndexValue = (ZERO = 0, ONE, TWO, THREE, FOUR);

var
  MyArray: array[Low(TIndexValue) .. High(TIndexValue)] of String;

Затем на элементы из этого массива можно ссылаться, используя значения TIndexValue в качестве индекса:

MyArray[ZERO] := 'abc';

Я пытаюсь получить ту же общую функциональность с помощью файла TStringList.

Одним из простых решений является приведение каждого значения индекса к типу Integer во время ссылки:

MyStringList[Integer(ZERO)] := 'abc';

Другое решение (чтобы скрыть все приведения) состоит в том, чтобы создать подкласс TStringList и отложить все приведения к подпрограммам этого подкласса, которые обращаются к унаследованному свойству Strings:

type
  TIndexValue = (ZERO = 0, ONE, TWO, THREE, FOUR);

type
  TEIStringList = class(TStringList)
  private
    function GetString(ItemIndex: TIndexValue): String;
    procedure SetString(ItemIndex: TIndexValue; ItemValue: String);
  public
    property Strings[ItemIndex: TIndexValue]: String 
      read GetString write SetString; default;
  end;

function TEIStringList.GetString(ItemIndex: TIndexValue): String;
begin
  Result := inherited Strings[Integer(ItemIndex)];
end;

procedure TEIStringList.SetString(ItemIndex: TIndexValue; ItemValue: String);
begin
  inherited Strings[Integer(ItemIndex)] := ItemValue;
end;

Это прекрасно работает для одной реализации, использующей перечисляемый тип TIndexValue.

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

Возможно ли что-то подобное? Я подозреваю, что мне, возможно, придется зависеть от Delphi Generics, но мне было бы очень интересно узнать, что есть более простые способы добиться этого.


person ardnew    schedule 20.02.2012    source источник
comment
Можете ли вы объяснить, почему array [TIndexValue] of string несовершенен?   -  person David Heffernan    schedule 20.02.2012
comment
Вы спрашиваете, почему я предпочитаю использовать TStringList вместо массива строк? Я написал несколько служебных функций, которые зависят от списков, а не от массивов, и я хотел бы сохранить их реализацию прежней.   -  person ardnew    schedule 20.02.2012
comment
Если бы вы использовали список строк, что помешало бы вам неоднократно вызывать Add и добавлять больше элементов, чем у вас было индексов?   -  person David Heffernan    schedule 20.02.2012
comment
Ха, хорошая мысль. Создание списков удобно с использованием значений перечисления, но код, обрабатывающий списки, может только уменьшить количество элементов.   -  person ardnew    schedule 20.02.2012
comment
Элементы не будут перемещаться в списке. Значения объединяются или исключаются, поэтому структура списка используется только из-за служебных функций, с которыми она взаимодействует.   -  person ardnew    schedule 20.02.2012
comment
И я не должен был говорить уменьшить количество элементов в предыдущем комментарии. В качестве значений будут установлены пустые строки, но расположение элемента не изменится.   -  person ardnew    schedule 20.02.2012
comment
Я бы использовал тип массива из моего первого комментария и заставил бы вспомогательные методы получать открытый массив строк в качестве своих параметров.   -  person David Heffernan    schedule 20.02.2012
comment
вам действительно нужно TStringList? может быть TDictionary<TIndexValue, string> лучше?   -  person teran    schedule 20.02.2012
comment
также вы можете создать общий класс-потомок, такой как TMyStringList<T : ordinal> с property MyItems[idx : T] : string read..., а затем преобразовать T в целое число в методе get. но в любом случае, если вам нужен только контейнер для хранения строки и доступа к ней с помощью вашего TIndexValue, тогда TDictionary будет лучшим решением;   -  person teran    schedule 20.02.2012
comment
@teran: пожалуйста, прочитайте предыдущие комментарии. Предложение Дэвида Хеффернана по использованию массивов является наиболее разумным и адаптируемым для моей ситуации (т.е. списки с известной максимальной длиной).   -  person ardnew    schedule 20.02.2012
comment
@teran: Насколько я знаю, нет общего ограничения, ограничивающего T только порядковыми номерами. ordinal не работает, а даже если бы и работало - Enum не является порядковым номером; его значения   -  person Chris    schedule 20.02.2012
comment
Посмотрите тему array properties в прекрасном мануале. В качестве индекса можно использовать практически любой тип! Однако в вашей идее есть недостаток, Count теряет смысл и всегда должен быть равен High (TIndexOrdinal) - Low (TIndexOrdinal) + 1.   -  person OnTheFly    schedule 20.02.2012
comment
@Chris, перечисляемые типы являются подмножеством порядковых типов.   -  person OnTheFly    schedule 20.02.2012
comment
@ user539484: вопрос касался TStringList, а не array. Решение array уже было опубликовано в вопросе. Крис говорил, что T не обязательно является порядковым типом, поэтому приведение Integer недопустимо.   -  person ardnew    schedule 20.02.2012
comment
@ardnew, я говорил вам о свойствах массива, а не о типах массива. Во втором случае тип индекса должен быть порядковым, в первом - нет. Посмотрите, например, на TStrings.Values.   -  person OnTheFly    schedule 21.02.2012
comment
@ Дэвид: я согласен. Я не вижу причин НЕ использовать array[TIndexType] of string. TList‹T›, TStringList и т. д. здесь не имеют преимуществ.   -  person Rudy Velthuis    schedule 21.02.2012


Ответы (2)


Я думаю, что дженерики были бы самым элегантным решением. Использовать их было бы так же просто, как переписать ваш класс выше, как:

TEIStringList<T> = class(TStringList) 

а затем замените все ссылки TIndexValue на T. Затем вы можете создать его так же, как и любой другой универсальный:

var
  SL: TEIStringList<TIndexValue>;
begin
  SL:=TEIStringList<TIndexValue>.Create;
  (...)
  ShowMessage(SL[ZERO])
  (...)
end;

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

type
  TIndexValueHolder = record
    Value : TIndexValue;
    class operator Implicit(A: TMyRecord): integer;
  end;

(...)

class operator TIndexValueHolder.Implicit(A: TMyRecord): integer;
begin
  Result:=Integer(A);
end;

Затем используйте с:

var
  Inx : TIndexValueHolder;

begin
  Inx.Value:=ZERO;
  ShowMessage(SL[Inx]);
end

ОБНОВЛЕНИЕ. Вы можете адаптировать TIndexValueHolder для использования в цикле for или while, добавив методы Next, HasNext и т. д. Однако это может закончиться поражением цели. Я до сих пор не уверен, какова цель или почему это было бы полезно, но в любом случае вот несколько идей о том, как это сделать.

person boileau    schedule 20.02.2012
comment
Кстати, отрицательный ответ на другой ответ почти отбил у меня желание сделать собственную попытку ответа. Его ответ настолько плох, что вы решили наказать его за попытку? - person boileau; 21.02.2012
comment
это вопрос ко мне? Я не был тем, кто минусовал его. Спасибо за ваш ответ, хотя - person ardnew; 21.02.2012
comment
@ardnew Извините, я думал, что это вы. Тем не менее, я все еще думаю, что отрицательный голос был неуместным, если только виновник не прокомментирует уважительную причину. - person boileau; 21.02.2012

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

type
  TEnum1 = (Zero = 0, One, Two, Three, Four);
  TEnum2 = (Nul = 0, Een, Twee, Drie, Vier);
  TEnum3 = (Gds = 0, Psajs, Oeroifd, Vsops, Wowid);

  TStringListHelper = class helper for TStringList
  private
    function GetString(Index: Variant): String;
    procedure SetString(Index: Variant; const Value: String);
  public
    property Strings[Index: Variant]: String read GetString write SetString;
      default;
  end;

function TStringListHelper.GetString(Index: Variant): String;
begin
  Result := inherited Strings[Index];
end;

procedure TStringListHelper.SetString(Index: Variant; const Value: String);
begin
  inherited Strings[Index] := Value;
end;

Тестовый код:

procedure TForm1.Button1Click(Sender: TObject);
var
  Strings: TStringList;
begin
  Strings := TStringList.Create;
  try
    Strings.Add('Line 1');
    Strings.Add('Second line');
    Strings[Zero] := 'First line';
    Memo1.Lines.Assign(Strings);
    Caption := Strings[Psajs];
  finally
    Strings.Free;
  end;
end;

Просмотрите историю изменений для предыдущей менее успешной попытки.

person NGLN    schedule 20.02.2012
comment
В чем преимущество хелперов класса по сравнению, скажем, с простым наследованием или делегированием в этой ситуации? - person Rudy Velthuis; 23.02.2012
comment
@Rudy Это позволяет избежать необходимости в нескольких производных. С помощью этого единственного помощника вы обрабатываете все перечисления одновременно. Конечно, промежуточный TEnumStringList не нужен (помощник может быть и для самого TStringList), но это было вдохновлено самим OP. - person NGLN; 25.02.2012
comment
@NGLN: с производным классом вы можете сделать то же самое. Я все еще не вижу необходимости в помощнике класса. - person Rudy Velthuis; 25.02.2012