Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cast generic T to a TObject?

I have a method that needs to return an object. Of course, it only makes sense if the T is an object:

function TGrobber<T>.Swipe: TObject;
var
   current: T;
begin
    {
       If generic T is not an object, then there's nothing we can return
       But we'll do the caller a favor and not crash horribly.
    }
    if PTypeInfo(TypeInfo(T))^.Kind <> tkClass then
    begin
       Result := nil;
       Exit;
    end;

    //We *are* an object, return the object that we are.
    current := Self.SwipeT;

    Result := TObject(current); <--E2089 invalid class typecast
end;

If T is not an object (e.g. an Integer, String, or OleVariant), then it will return nil, and not crash horribly.

If we are an object (e.g. TCustomer, TPatron, TSalesOrder, TShape), then we can return the object just fine.

I didn't want to confuse the issue; but if you look at IEnumerable, you'll see what is actually going on.

Bonus Reading

  • Delphi: determine actual type of a generic?
  • Conditional behaviour based on concrete type for generic class

Answer

I'll let TLama copy/paste the answer to get his credit:

function TGrobber<T>.Swipe: TObject;
var
   current: T;
   v: TValue;
begin
    current := Self.SwipeT;
    v := TValue.From<T>(current);
    {
       If generic T is not an object, then there's nothing we can return
       But we'll do the caller a favor and not crash horribly.
    }
    if not v.IsObject then
    begin
       Result := nil;
       Exit;
    end;

    Result := v.AsObject;
end;
like image 518
Ian Boyd Avatar asked Feb 04 '15 01:02

Ian Boyd


1 Answers

I see two main options. If the generic type must be a class type, and this is know at compile time, you should apply a constraint to the type:

type
  TGrobber<T: class> = class
    ....
  end;

Or if the type must derive from a specific class then that constraint can be specifies like so:

type
  TGrobber<T: TMyObject> = class
    ....
  end;

Once the constraint is applied then direct assignment is all you need.

Result := current;

This becomes possible because the compiler enforces the constraint on your generic type. And so knows that the assignment is valid for all possible instantiations.

I would comment that it seems odd for a generic class to have a function returning TObject. Why doesn't your function return T?

If you cannot constrain then a simple pointer type cast is the cleanest approach:

Result := PObject(@current)^;

Obviously you need to check that T is a class type, code for which you have already demonstrated mastery.

For what it is worth, since Delphi XE7 it is simpler to check the kind of a type using System.GetTypeKind:

if GetTypeKind(T) = tkClass then
  Result := PObject(@current)^
else
  Result := nil;
like image 54
David Heffernan Avatar answered Oct 12 '22 23:10

David Heffernan