Basic issue with non matching var
parameters is the possibility that you end up with wrong type inside the calling variable.
You can trick the compiler to do that by using absolute
keyword - that allows you to declare variables of different type that share same space - and simulate what would happen if compiler would allow you to use such construct.
Consider following example
uses
System.SysUtils;
type
TAnimal = class
public
procedure Run; virtual;
end;
TDog = class(TAnimal)
public
procedure Bark; virtual;
procedure Fetch; virtual;
end;
TCat = class(TAnimal)
public
procedure Meow; virtual;
end;
procedure TAnimal.Run;
begin
Writeln('Run');
end;
procedure TDog.Bark;
begin
Writeln('Bark');
end;
procedure TDog.Fetch;
begin
Writeln('Fetch');
end;
procedure TCat.Meow;
begin
Writeln('Meow');
end;
procedure Move(const aA: TAnimal);
begin
aA.Run;
end;
procedure Train(var aA: TAnimal);
begin
aA := TCat.Create;
end;
var
Dog: TDog;
Cat: TAnimal absolute Dog;
begin
try
// we cannot use Dog here, because compiler would refuse to compile such code
// Cat is TAnimal and compiler allows to pass it
// since Dog and Cat variables share same address space that is
// equivalent of calling Train(Dog);
Train(Cat);
Move(Cat);
Dog.Bark;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
If you run above code you will get following output
Run
Meow
Dog
and Cat
variables share the same address space, so when you call Train(Cat)
as the result you will get TCat
instance that you can use either through Cat
or Dog
variable. Basically, you will end up with TCat
instance inside TDog
variable.
Clearly, when you call Dog.Bark
, you should get Bark
as output not Meow
. Meow
is the first method in TCat
just like the Bark
is the first method in TDog
, and when resolving the Bark
address through TCat
virtual method table it will find Meow
method instead. Since both methods have same signature everything is fine if you consider wrong output as being fine.
Now, if you try calling the Dog.Fetch
, the application will crash with AV. There are no matching methods at corresponding address in TCat
class and you are basically calling some uninitialized place in memory instead of proper method.
That explains why var
or out
parameter types must match the caller variable type.
As to why you can pass TDog
or TCat
as TAnimal
const
or value parameter. Both TDog
and TCat
inherit from TAnimal
and whatewer you can do with TAnimal
instance, both TDog
and TCat
support it. They can override particular behavior, so your cat can run differently than your dog, but whatever you do it is well defined. You cannot end up running some inexistent code.
procedure Move(const aA: TAnimal);
begin
aA.Run;
aA.Fetch; // this will fail to compile - there is no Fetch method in TAnimal class
end;
Of course, this does not prevent you to test for particular class and use type casts to call Fetch
if TAnimal
actually is a TDog
.
procedure Move(const aA: TAnimal);
begin
aA.Run;
if aA is TDog then TDog(aA).Fetch;
end;
However, if you abuse typecasting and typecast without checking whether particular variable is actually a TDog
instance, you will again trip into AV.
procedure Move(const aA: TAnimal);
begin
aA.Run;
TDog(aA).Fetch;
end;