tags:

views:

300

answers:

3

I need a way to write a generic procedure to act upon an object type or any of its descendants.

My first attempt was to declare

procedure TotalDestroy(var obj:TMyObject);

but when using it with a descendant object

type TMyNewerObject = class(TMyObject);
var someNewerObject: TMyNewerObject;

TotalDestroy(someNewerObject);

I get the infamous error "types of formal and actual parameters must be identical"

So, while strugling to find a solution, I looked at the source code of Delphi system FreeAndNil procedure. And I found this awesome declaration, along with this astonishing comment

{ FreeAndNil frees the given TObject instance and 
  sets the variable reference to nil.  
  Be careful to only pass TObjects to this routine. }

procedure FreeAndNil(var Obj);

It avoids the type checking error, but it uses no safety net.

My question is ... is there any safe way to check the type of an untyped var parameter?

or in other words, can you improve this Delphi source code so that the warning would not be needed?

procedure FreeAndNil(var Obj);
var
  Temp: TObject;
begin
  Temp := TObject(Obj);
  Pointer(Obj) := nil;
  Temp.Free;
end;
+8  A: 

Let's examine what you want to do.

You want to call a method that takes X, passing in an object of type Y, where Y is a descendant of X. The snag, the parameter is a "var" parameter.

Let's analyze what you could do if that was possible.

type
    TBase = class
    end;
    TDescendant = class(TBase)
    end;

procedure Fiddle(var x: TBase);
begin
    x := TDescendant.Create;
end;

type
    TOtherDescendant = class(TBase)
    end;

var a: TOtherDescendant;
a := TOtherDescendant.Create;
Fiddle(a);

Uh-oh, now a no longer contains an instance of TOtherDescendant, it contains an instance of TDescendant. That probably comes as a surprise to the code that follows the call.

You must not only consider what you intend to do with the syntax you propose, but effectively what you could do with the syntax.

You should read Eric Lipperts excellent blog post about similar issues in .NET, found here: Why do ref and out parameters not allow type variation?.

Lasse V. Karlsen
thanks for your insight and for the link. As I commented below, my intention is to finally nil the reference.
PA
+3  A: 

In addition to what Lasse wrote, which is quite correct, most of the time you don't want to pass an object to a var parameter anyway.

An object is a reference type. What you see as the object is actually a reference to it. You would only want to pass an object reference to a var parameter if you wanted to change your object out for a new object. If you just want to be able to modify the members of the object, then you can do that by simply passing it to a normal parameter. Make method call take a TMyObject parameter instead of a var TMyObject parameter and it should work.

Of course, if you really are replacing the object, then feel free to disregard all this, and see Lasse's answer.

Mason Wheeler
Agreed, I didn't think of that angle. Quite correct, you don't need "var" if all you want is the reference.
Lasse V. Karlsen
in fact, I just wanted to nil it, performing some special clean up first.
PA
+6  A: 

I've written about this before, using an example very similar to Lasse's:

Unless you're writing an assignment statement to change the value of the input parameter itself, and not just one of its properties, you shouldn't pass a parameter by reference in the first place.

If you are writing an assignment statement to change the parameter's value, then the compiler message really is true, and you should heed it.

One reason for needing to by-pass the error is when you're writing a function like TApplication.CreateForm. Its job is to change the input parameter's value, and the type of the new value varies and cannot be determined at compile time. If you're writing such a function, then your only option with Delphi is to use an untyped var parameter, and then there is extra burden on both the caller and the receiver to make sure everything goes right. The caller needs to make sure it passes a variable that is capable of holding values of whatever type the function will put in it, and the function needs to make sure it stores a value of a type compatible with what the caller requested.

In the case of CreateForm, the caller passes in a class-reference literal and a variable of that class type. The function instantiates the class and stores the reference in the variable.

I don't think very highly of either CreateForm or FreeAndNil, largely because of the way their untyped parameters sacrifice type safety in return for comparatively little extra convenience. You haven't shown the implementation of your TotalDestroy function, but I suspect its var parameter will ultimately provide the same low utility as in those other two functions. See my articles on both:

Rob Kennedy
As you cleverly guessed, my TotalDestroy does some special clean up of TMyObject and finally does a FreeAndNil.
PA
If there's special cleanup required for a `TMyObject`, consider putting that in the destructor. Don't force consumers of your class to learn a new way of destroying it when plain old `Free` works fine for everything else.
Rob Kennedy
Thanks Rob. I take your point, I don't need my TotalDestroy. I will go just with my Destroy destructor I already have. And let the user decide if they want it thru FreeAndNil or not.
PA