Lawrence Barsanti
Lawrence Barsanti

Reputation: 33232

Is there a concise way to update all the values in TDictionary of records?

I would like to do something like this but it won't compile because Pair cannot be assigned to.

var
  MyDictionary: TDictionary<TGuid, TCustomRecord>;
  Pair: TPair<TGuid, TCustomRecord>;
begin
  // ... create and populate my dictionary ...

  foreach Pair in MyDictionary do
  begin
    PairRef.Value.MyField := PairRef.Value.MyField + 1;
  end;
end

Just to be clear, I know how to accomplish this with more code, I'm looking for something that is concise and easy to read.

Upvotes: 5

Views: 1842

Answers (2)

David Heffernan
David Heffernan

Reputation: 612964

There is no iterator on TDictionary that returns a reference to a value. All the iterators provide values and that means that what you are asking for is not possible with the current design.

In other languages, for example C++ and D that I know, references are first class citizens in the language. You can easily write iterators that enumerate references rather than values. That's what you need to solve your problem concisely. Unfortunately the language is lacking.

One obvious option would be to switch to using reference types (class) rather than value types (record). That would solve the iteration problem in a stroke because would be iterating over references. However, one usually chooses to use value types for a good reason and you may have constraints that stop you making this switch.

Another possibility would be to write a container that offered iterators that provided pointers to the values. That's as close as you can get to a reference to a record. But you would have to roll your own container.

Upvotes: 1

Sir Rufo
Sir Rufo

Reputation: 19106

Here is a simple program which shows the different handling using records and objects with a TDictionary.

program Project1;

{$APPTYPE CONSOLE}
{$R *.res}

uses
  System.SysUtils, System.Generics.Collections;

type
  TMyRecord = record
    Field : Integer;
  end;

  TMyObject = class
    Field : Integer;
  end;

procedure UseObjectDict;
var
  LDict :  TDictionary<TGUID, TMyObject>;
  LValue : TMyObject;
begin
  write( 'TMyObject: ' );

  LDict := TObjectDictionary<TGUID, TMyObject>.Create( [doOwnsValues] );
  try

    // populate
    while LDict.Count < 10 do
    begin
      LDict.Add( TGuid.NewGuid, TMyObject.Create );
    end;

    // update
    for LValue in LDict.Values do
      begin
        LValue.Field := LValue.Field + 1;
      end;

    // output
    for LValue in LDict.Values do
      begin
        write( LValue.Field, ', ' );
      end;
    Writeln;

  finally
    LDict.Free;
  end;
end;

procedure UseRecordDict;
var
  LDict :  TDictionary<TGUID, TMyRecord>;
  LKey :   TGUID;
  LValue : TMyRecord;
begin
  write( 'TMyRecord: ' );
  LDict := TDictionary<TGUID, TMyRecord>.Create;
  try

    // populate
    while LDict.Count < 10 do
      begin
        LValue.Field := 0;
        LDict.Add( TGuid.NewGuid, LValue );
      end;

    // update
    for LKey in LDict.Keys do
      begin
        LValue.Field := LDict[LKey].Field + 1;
        LDict.AddOrSetValue( LKey, LValue );
      end;

    // output
    for LValue in LDict.Values do
      begin
        write( LValue.Field, ', ' );
      end;
    Writeln;

  finally
    LDict.Free;
  end;
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  try

    UseObjectDict;
    UseRecordDict;

  except
    on E : Exception do
      Writeln( E.ClassName, ': ', E.Message );
  end;

  ReadLn;

end.

Upvotes: 8

Related Questions