views:

274

answers:

2

Based on an earlier post, I've written the following code. Please excuse the verbosity of this post. I believe it's better for all parties to have the full code available to test and comment on.

program sandbox;
{$APPTYPE CONSOLE}

uses
  SysUtils,
  Generics.Collections;

type
  TDataType = class
    // Stuff common to TInt and TStr
  end;

  TInt = class(TDataType)
    FValue:  integer;
    constructor Create(Value, Low, High: integer);
  end;

  TStr = class(TDataType)
    FValue: string;
    constructor Create(Value: string; Length: integer);
  end;

  TSomeClass = class
    FIntList: TList<TInt>;
    FStrList: TList<TStr>;
    procedure AddToList<T: TDataType>(Element: T);
    constructor Create();
    procedure Free();
  end;

constructor TInt.Create(Value, Low, High: Integer);
begin
  inherited Create();
  FValue := Value;   
end;

constructor TStr.Create(Value: string; Length: Integer);
begin
  inherited Create();
  FValue := Value;
end;

procedure TSomeClass.AddToList<T>(Element: T);
begin
  if TObject(Element) is TInt then
    FIntList.Add(Element)
  else if TObject(Element) is TStr then
    FStrList.Add(Element);
end;

constructor TSomeClass.Create();
begin
  inherited;
  FIntList := TList<TInt>.Create();
  FStrList := TList<TStr>.Create();
end;

procedure TSomeClass.Free();
var
  SomeIntItem: TInt;
  SomeStrItem: TStr;
begin
  for SomeIntItem in FIntList do begin
    SomeIntItem.Free();
  end;

  for SomeStrItem in FStrList do begin
    SomeStrItem.Free;
  end;

  FIntList.Free();
  FStrList.Free();
end;

var
  Inst: TSomeClass;

begin
  try
    { TODO -oUser -cConsole Main : Insert code here }

    Inst := TSomeClass.Create;
    Inst.AddToList(TInt.Create(100, 0, 101));
    Inst.AddToList(TStr.Create('Test', 10));
    Inst.Free;

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

Note that the constructors of TInt and TStr in the real world would utilize the Low, High: integer and Length: integer parameters as well. I'm having an "E2089 Invalid typecast" at if TObject(Element) is TInt then and else if TObject(Element) is TStr then running Delphi 2009. Do anyone know why this happens?

Edit: Please note that TInt and TStr are just two of possibly 10-20 other types; otherwise overloading is the tool for the job. :)

+6  A: 

Rethink your design. You may just use overloading instead of a generic type parameter, like this:

procedure Add (SomeString : TString); overload;
procedure Add (SomeInt : TInt); overload;

Or if you want to use polymorphism do what Gamecat suggested and just pass the base type as the parameter, using is on that parameter:

procedure Add (Element : TDataType);

Like Rob pointed out in a comment to your previous question: it's not really generic if you allow only two types and have conditionals based on the actual type. So generics might be the wrong tool here.

Hope that helps.

Smasher
Its no compiler bug. Its missing a typecast ;-). But I agree with the wrong tool comment.
Gamecat
I misread the question. I have updated my answer.
Smasher
Thanks Smasher! I realize I should've pointed out from the outset that TInt and TStr will be accompanied by more than 10 other types as well. I was unaware of the TypeInfo() method, so +1 for that! :)
conciliator
You mean the one that I just deleted? :) Here's the link to Barry Kelly's original answer in case anyone wonders: http://stackoverflow.com/questions/805931/conditional-behaviour-based-on-concrete-type-for-generic-class/806002#806002
Smasher
Yup, that's the one! I realize it might be common knowledge, but I started using Delphi some four months ago, so I'm a rookie by all standards. And thanks for the design input as well.
conciliator
@conciliator: Even if you have 10+ different types, if you want to put them in 10+ different lists generics are not the way to go, and overloaded methods are better (+1 to this answer). And in any case, if you are relatively new to Delphi I'd strongly advise to stay away from generics unless there is a really compelling case for them - at least with Delphi 2009 we developers have been treated as a giant field test of this new feature, and you never really know whether you have f*ed up or the compiler is to blame when things don't work.
mghie
Thanks mghie, that makes sense. I've got some C# friends who're apparently using them all the time, so I wanted to give it a shot. Guess I'd better learn more about polymorphism before I dive into the generics.
conciliator
Conciliator, generics are useful when the *only* thing that differs is the data types. In your case, the *variables* differ, too. Generics are supposed to allow you to have a single implementation that's valid for multiple types, but your code requires *different* implementations based on the type. (For integers, add to one list; for strings, add to a totally different list.) That's not a case for generics, and your C# friends wouldn't use generics in this situation, either.
Rob Kennedy
Thats a good point, Rob. You know you have made the wrong design choice if you've decided what tools to use before you've considered the problem to be solved... I guess I just wanted to learn generics too badly. But I'm happy - I did learn a little, and I'll be sure to keep them in mind for the next opportunity of abuse! :)
conciliator
+3  A: 

The problem is not with the generics. You add a TDataType to a list that expects TInt or TStr:

procedure TSomeClass.AddToList<T>(Element: T);
begin
  if TObject(Element) is TInt then
    FIntList.Add(TInt(Element))
  else if TObject(Element) is TStr then
    FStrList.Add(TStr(Element));
end;

Solves the problem.

But why not use:

procedure TSomeClass.AddToList(Element: TDataType);
begin
  if Element is TInt then
    FIntList.Add(TInt(Element))
  else if Element is TStr then
    FStrList.Add(TStr(Element));
end;
Gamecat
+1 you obviously read the question better than I did.
Smasher
Thanks, Gamecat! The last example compiles nicely. However, I still get a typecast error using your first example (tried T: TDataType, T: class and empty constraints). Did you get it to compile?
conciliator
@Gamecat: perhaps I wasn't clear in the first place? It is the typecast in the statement "if TObject(Element) is TInt then" and "else if TObject(Element) is TStr then" that fails... Do you know why?
conciliator
I just tested your code. The `TObject(Element)`cast works just fine, while the `FIntList.Add (Element)` yields a "invalid type: T and TInt" error message - as Gamecat said.
Smasher
That's weird. I've uploaded a screen dump: http://img12.imageshack.us/img12/1208/typecasterror.png . What am I doing wrong?
conciliator
Which Delphi version are you using?
Smasher
I'm using CodeGear™ Delphi® 2009 Version 12.0.3420.21218
conciliator
Using Delphi 2010, I am able to what is expected, I did not think that parameterized methods worked in 2009.Try this:var O : TObject;begin O := Element if O is Element then ...end;
Robert Love