Question

I created the following class, after reading about the significant performance improvement of TDictionary over TStringList:

    TAnsiStringList = class(TObjectDictionary<AnsiString,TObject>)
    public
      constructor Create(const OwnsObjects: Boolean = True); reintroduce;
      destructor Destroy; override;
      procedure Add(const AString: AnsiString);
      procedure AddObject(const AString: AnsiString; AObject: TObject);
    end;

I coded the constructor like this:

  { TAnsiStringList }

    constructor TAnsiStringList.Create(const OwnsObjects: Boolean = True);
    begin
      if OwnsObjects then
        inherited Create([doOwnsKeys,doOwnsValues])
      else
        inherited Create;
    end;

...expecting that this TObjectDictionary constructor would be called:

    constructor Create(Ownerships: TDictionaryOwnerships; ACapacity: Integer = 0); overload;

...if the Ownerships parameter were specified. If the Ownerships parameter is not specified, I expected that the following inherited TDictionary constructor would be called:

    constructor Create(ACapacity: Integer = 0); overload;

The code compiles and runs, but when I call

    inherited Create([doOwnsKeys,doOwnsValues]) I get the following error:

Invalid class typecast

Does anyone see what I'm doing wrong, and is there a proper way to do this?

TIA

Was it helpful?

Solution

The problem is that you are asking the container to call Free on your keys when items are removed. But your keys are not classes and so that is an invalid request. This is trapped at runtime rather than compile time because the ownership is not determined until runtime.

You only want doOwnsValues and should remove doOwnsKeys.

 if OwnsObjects then
    inherited Create([doOwnsValues])
 else
   inherited Create;

For what it is worth, if you are trying to maked an AnsiString equivalent to TStringList, then your approach is flawed. A string list is an ordered container, but a dictionary is not. You won't be able to index by integer, iterate in order and so on. I also do not understand why you would want to force all consumers of this class to declare the objects to be of type TObject. You should leave that parameter free for the consumer to specify. That's the beauty of generics.

Perhaps you don't want an ordered container, in which case a dictionary is what you need. But in that case you simply don't need to create a new class. You can simply use TObjectDictionary as is.

If you are dead set on creating a sub class then I'd do it like this:

type
  TAnsiStringDict<T: class> = class(TObjectDictionary<AnsiString, T>)

That will allow the consumer of the class to decide which type of objects they put in the dictionary, and maintain compile time type safety.

So, when you want a dictionary of list boxes your declare a variable like this:

var
  ListBoxDict: TAnsiStringDict<TListBox>;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top