Indexed property – problem z modyfikacją pola rekordu

Odpowiedz Nowy wątek
2019-04-05 17:06
0

Chcę sobie napisać rekord, który jednocześnie będzie jakby dynamiczną tablicą rekordów, obudowaną w funkcje i nie mogę sobie poradzić z uzyskaniem właściwego rezultatu.

type
  TCell = record
    Value: Integer;
  end;

  TList = record
  strict private
    ConCell: array of TCell;
    function GetCell(Index: Integer): TCell;
    property Cell[Index: Integer]: TCell read GetCell; default;
  public
    procedure SetSize(pSize: Integer);
    procedure Clear;
  end;

var
  List: TList

=====

function TList.GetCell;
begin
  Result:= ConCell[Index];
end;

Dzięki takiej konstrukcji mogę raz "zarządzać" całą listą i np. chcąc ją wyczyścić albo zmienić jej wielkość, robię tak:

List.Clear;
List.SetSize(4);

A potem chcąc uzyskać dostęp do poszczególnych komórek listy, robię tak:

JakasZmiena:= List[0].Value;

zamiast:

JakasZmienna:= List.ConCell[0].Value;

Teoretycznie niewiele, ale to eleganckie rozwiązanie :). Sęk w tym, że to działa tylko przy odczycie i nie wiem jak to zakodować tak, żeby pozwoliło mi także zapisywać poszczególne zmienne w komórkach. Właściwość PropCell jako metodę zapisu (write) łyknie tylko coś takiego (z uwagi na zgodność typów):

procedure SetCell(Index: Integer; pCell: TCell);

Ale to odpada, bo w ten sposób mogę tylko przypisywać całe komórki, np.:

List[0]:= JakasKomorka; //JakasKomorka to zmienna typu TCell

Na coś takiego już mi nie pozwoli:

List[0].Value:= 167;

Da się to jakoś obejść?

edytowany 12x, ostatnio: furious programming, 2019-04-09 16:18

Pozostało 580 znaków

2019-04-05 17:39
0

Niestety nie, jest to ograniczenie tej składni.


Pozostało 580 znaków

2019-04-05 20:04
1

Jak wspomniał @Patryk27, jest to ograniczenie języka, ale da się to rozwiązać dodając właściwość do struktury TCell. Bo w takim przypadku musi być użyta właściwość, a nie pole.

Nie musi ona posiadać settera w postaci metody – wystarczy bezpośredni dostęp do pola.

Przykład deklaracji:

type
  TItem = record
  private
    FData: Integer;
  public
    property Data: Integer read FData write FData;
  end;

type
  TList = record
  private
    FList: array [0..3] of TItem;
  private
    function GetItem(AIndex: Integer): TItem;
    procedure SetItem(AIndex: Integer; AItem: TItem);
  public
    property Items[AIndex: Integer]: TItem read GetItem write SetItem; default;
  end;

  function TList.GetItem(AIndex: Integer): TItem;
  begin
    Result := FList[AIndex];
  end;

  procedure TList.SetItem(AIndex: Integer; AItem: TItem);
  begin
    FList[AIndex] := AItem;
  end;

I przykład użycia:

var
  List: TList;
begin
  List[0].Data := 10; // składnia dozwolona
end.

To powoduje, że właściwość TItem.Data umożliwia aktualizowanie danych struktury pojedycznej pozycji, a właściwość TList.Items pozwala odczytywać i modyfikować całe rekordy pozycji.

W powyższym przykładzie posłużyłem się macierzą o statycznym rozmiarze, żeby nie zaśmiecać przykładowego kodu informacjami, których problem nie dotyczy.


Pytanie dodatkowe – dlaczego bawisz się rekordami, zamiast skorzystać z gotowego kontenera, np. TList?


edytowany 10x, ostatnio: furious programming, 2019-04-05 20:15

Pozostało 580 znaków

2019-04-05 22:18
0

Gdy korzystam z twojego przykładu, to wywala mi błąd: E2064 Left side cannot be assigned to

A dlaczego nie użyję TList? Bo to był tylko zmyślony przykład :). Tak na prawdę mam rekord, który grupuje powierzchnie ("Faces") i ma kilka wbudowanych metod do grupowej obsługi tych powierzchni. Czasem jednak potrzebna jest ingerencja indywidualna. Grupowo używam tego tak:

Faces.ReadNormals;

A indywidualnie tak:

Faces.Face[13].ReadNormal;

No i pomyślałem sobie, że fajnie by było móc to skrócić i pisać tak:

Faces[13].ReadNormal;
edytowany 7x, ostatnio: furious programming, 2019-04-05 22:40

Pozostało 580 znaków

2019-04-05 22:42
0

Kod testowałem pod Lazarusem i działa prawidłowo. ;)

Crow napisał(a):

A indywidualnie tak:

Faces.Face[13].ReadNormal;

No i pomyślałem sobie, że fajnie by było móc to skrócić i pisać tak:

Faces[13].ReadNormal;

No tak, tyle że to jest samo ukrycie właściwości (?) Face, a to robi się za pomocą default. No to jak w końcu, chodzi o stworzenie właściwości domyślnej, czy dodaniu możliwości modyfikacji poszczególnych pól struktur?


edytowany 2x, ostatnio: furious programming, 2019-04-05 22:43

Pozostało 580 znaków

2019-04-05 23:10
0

Chodzi o możliwość pełnego korzystania z zawartości rekordu TFace (czyli zapis i odczyt pól, wywoływanie metod), znajdującego się - w formie tablicy - wewnątrz rekordu TFaces, z jednoczesnym ukryciem odwoływania się do niego, czyli np.:

TFace = record
  Value: Integer;
  Low: Double;
  Metoda1
end;

TFaces = record
strict private
  CFace: array of TFace;
  function GetFace(Index: Integer): TFace;
  procedure SetFace(Index: Integer; TFace);
  property Face[Index: Integer]: TFace read GetFace write SetFace; default;
public
  Metoda1;
  Metoda2;
end;

Użycie:

Faces[123].Value:= 76;
Zmienna:= Faces[14].Low;
Faces[16].Metoda1;

zamiast

Faces.Face[123].Value:= 76;
Zmienna:= Faces.Face[14].Low;
Faces.Face[16].Metoda1;

A co do twojego kodu, to zrobiłem kopiuj / wklej i wyskakuje wspomniany błąd :(.

edytowany 5x, ostatnio: furious programming, 2019-04-06 04:09

Pozostało 580 znaków

2019-04-06 04:14
1
Crow napisał(a):

Chodzi o możliwość pełnego korzystania z zawartości rekordu TFace (czyli zapis i odczyt pól, wywoływanie metod), znajdującego się - w formie tablicy - wewnątrz rekordu TFaces, z jednoczesnym ukryciem odwoływania się do niego […]

W dalszym ciągu nie wiem dlaczego używasz rekordów i dlaczego sam implementujesz listę (w dodatku w postaci rekordu), zamiast skorzystać z klasy (do opisu pojedynczej ”twarzy”) oraz z wbudowanego generycznego kontenera pokroju TList (do przechowywania listy tych ”twarzy”). Dorobiłbyś sobie settery i mógłbyś modyfikować co tylko chcesz, nawet pojedynczo, i też mógłbyś mieć domyślne indeksowane właściwości.

Robisz tak dlatego, że pracujesz nad jakimś tam projektem i nie możesz tego zmienić, czy z innego powodu?

A co do twojego kodu, to zrobiłem kopiuj / wklej i wyskakuje wspomniany błąd :(.

Widać FPC w tym przypadku różni się od Delphi, pozwalając na taką konstrukcję. Innego rozwiązania nie znam.


edytowany 6x, ostatnio: furious programming, 2019-04-06 04:18

Pozostało 580 znaków

2019-04-07 16:53
0

Nie zmieniam na TList, bo cały projekt zbudowałem na bazie tablic i teraz nie chce mi się wszystkiego przerabiać :). Może w następnej wersji projektu spróbuję. Tak czy inaczej dzięki! ;].

edytowany 1x, ostatnio: furious programming, 2019-04-07 17:04
Nie cytuj całego posta, jeśli odpowiadasz zaraz pod nim i do jego całości się odnosisz – wątek niepotrzebnie się rozciąga. :P - furious programming 2019-04-07 17:05

Pozostało 580 znaków

2019-04-09 09:57
0

Jednak muszę odkopać (przepraszam), bo coś nie działa...

TFace = record
  Value: Integer;
end;

TFaces = class(TList<TFace>)
  //Jakies metody
end;

var
  F: TFace;
  Faces: TFaces;

Faces:= TFaces.Create;
F.Value:= 0;
Faces.Add(F);
Faces[0].Value:= 123;

Dostaję error:

E2064 Left side cannot be assigned to

Próbowałem też dodać do TFace właściwość. Bezpośrednie wskazanie na zmienną stanowiącą kontener (read cValue write cValue) dawało ten sam error, skompilowało się dopiero po dodaniu gettera i settera... tyle, że całość nadal nie działa i zmienna się nie nadpisuje.

procedure TFaces.SetValue(pValue: Integer);
begin
  cValue:= pValue; //cValue to zmienna kontenera, z której odczytuje i do której zapisuje właściwość Value;
end;

Faces:= TFaces.Create;
F.Value:= 0;
Faces.Add(F);
Face[0].Value:= 123; //kompiluje się ale nadal jest tam zero

Działa tylko coś takiego:

Faces:= TFaces.Create;
F.Value:= 0;
Faces.Add(F);
F.Value:= 100;
Faces[0]:= F;

No ale coś takiego mnie nie urządza, bo ja chcę indywidualnego dostępu do pól rekordu, bez każdorazowego nadpisywania całego rekordu.

edytowany 9x, ostatnio: furious programming, 2019-04-09 16:01

Pozostało 580 znaków

2019-04-09 16:09
1
Crow napisał(a):

Dostaję error:

E2064 Left side cannot be assigned to

To jest ten sam problem co wcześniej – jeśli właściwość zwraca rekord, to nie możesz modyfikować jego pól bezpośrednio. Analogicznie do mojego poprzedniego przykładu, spróbuj w taki sposób:

type
  TItem = record
  private
    FData: Integer;
  public
    property Data: Integer read FData write FData;
  end;

type
  TList = class(TList<TItem>);

i użycie:

var
  List: TList;
  Item: TItem;
begin
  List := TList.Create();

  Item := TItem.Create();
  Item.Data := 10;

  List.Add(Item);
  List[0].Data := 20; // sprawdź czy działa

Jeśli działa to popraw swój kod, a jak nie to wymień rekordy na zwykłe, poste klasy. A jeśli potrzebujesz typu zarządzanego, alokowanego na stosie, to spróbuj w podobny sposób wykorzystać stare obiekty.


edytowany 2x, ostatnio: furious programming, 2019-04-09 16:17

Pozostało 580 znaków

2019-04-11 10:51
0

Dokładnie tak samo próbowałem to rozwiązać, ale nie działa. Na szczęście udało się z klasami prostymi, z którymi, z jakiegoś powodu nie ma problemu. Wszystko się kompiluje, a wartości zostają nadpisane poprawnie (nawet nie muszę używać właściwości, zwykłe pola wystarczą). Dzięki, zobaczę w praniu, czy wszystko zadziała jak należy :).


Edit: A jednak nie ;/.

type
  TFace = class(TObject)
    class var Value: Integer;
  end;

  TFaces = class(TList<TFace>)
    //Jakies metody
  end;

Faces:= TFaces.Create;
  Face.Value:= 0;
  Faces.Add(Face);
  Faces.Add(Face);
  Faces[0].Value:= 123;
  Faces[1].Value:= 345;
  ShowMessage(IntToStr(Faces[0].Value));
  ShowMessage(IntToStr(Faces[1].Value));

Zwraca dwa razy 345, czyli przypisanie do obojętnie którego elementu listy, nadpisuje całą :(.

edytowany 6x, ostatnio: furious programming, 2019-04-11 16:08

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0