Entwickler-Ecke

Sonstiges (Delphi) - (x as TControl) / TControl(x) - Wo ist der Unterschied?


FD-83 - So 27.02.05 17:11
Titel: (x as TControl) / TControl(x) - Wo ist der Unterschied?
Wo genau liegt eigentlich der Unterschied zwischen den beiden TypeCast Varianten?


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
procedure TForm1.Button1Click(Sender: TObject);
begin
  (Sender as TButton).Caption := 'klick';
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TButton(Sender).Caption := 'klack';
end;

Funktionieren tut ja beides. Sind das 2 Schreibweisen für die gleiche Sache oder gibts wirklich einen Unterschied?

Gruss Frederik


Larus - So 27.02.05 17:37

Was macht das überhaupt?


FD-83 - So 27.02.05 17:59

Das nennt man Casting. Damit kann man Objekte von einem Klassen- oder interfaceTyp in einen anderen Umwandeln. Vorrausgesetzt sie sind in der selben Vererbungshirachie bzw. implementieren das entsprechende Interface.

Im Beispiel oben hast du den Sender des Ereignisses (also den Button) nur als TObject vorliegen. Ein TObject hat aber keine Eigenschaft Caption. Daher muss man die TObject Variable erst in eine vom Typ TButton casten. Das funktioniert, weil ein TButton (so wie alle anderen Klassen) vom Typ TObject abstammen.

So ganz grob. Richtig verstehen kann man das nur, wenn man sich etwas mehr mit OOP beschäftigt...

Gruß
Frederik


Larus - So 27.02.05 18:13

achso also mit anderen Worten.... Nur der Button der gerade auf diese Funktion zugreift wird umbenannt? (Wie im Beispiel)


delfiphan - So 27.02.05 18:29

Larus hat folgendes geschrieben:
achso also mit anderen Worten.... Nur der Button der gerade auf diese Funktion zugreift wird umbenannt? (Wie im Beispiel)

Mit anderen Worten? Die Aussage ist oben nirgends enthalten. Es wird auch nichts umbenannt sondern der Typ wird gecastet.

TButton(Sender) ist ähnlich wie wenn man Byte('A') schreibt, um raus zu finden welche Nummer in der Ascii-Tabelle das Zeichen 'A' hat. TButton(Sender) heisst einfach, was immer bei der Speicheradresse für Sender ist, soll jetzt ein TButton sein.
Ich denk in Delphi 1 kannst du sogar String(F) schreiben (meinetwegen "String((@F)^)" in späteren Versionen), wobei F ein real ist. Da bekommst du dann ziemlichen Müll raus. Da musst du aufpassen.
"Sender as TButton" liefert eine Referenz auf "Sender" mit dem Typ TButton. Kann "Sender" gar kein TButton sein, dann gibt es eine Exception. "Sender as TButton" erzeugt mehr Code als "TButton(Sender)". Das "as" geht natürlich nur mit Klassen.


wulfskin - So 27.02.05 18:32

Larus hat folgendes geschrieben:
achso also mit anderen Worten.... Nur der Button der gerade auf diese Funktion zugreift wird umbenannt? (Wie im Beispiel)
Es wird die Beschriftung des Buttons geändert, der gedrückt wurde und deshalb als Sender übergeben wurde.

Der Unterschied liegt daran, dass   (Sender as TPanel).Xy eine Fehlermeldung ausgibt, wenn Sender nicht vom Typ Panel ist. Der andere Typcast ignoriert das Stillschweigend und das Programm läuft weiter, sofern das Object die Eigenschaft trotzdem besitzt. Sie wird aber nicht geändert.

Gruß Hape!

//Edit: delfiphans Erklärung ist wohl besser ;)!


Larus - So 27.02.05 18:35

gut zu wissen :) vieleicht kann man es irgentwanneinmal brauchen. :idea: außerdem is man ja ständig am weiterlesen und lernen :wink:


delfiphan - So 27.02.05 18:42

Übrigens, mit "if Sender is TButton" kannst du überprüfen, ob Sender ein TButton ist...


delfiphan - So 27.02.05 18:46

wulfskin hat folgendes geschrieben:
Der andere Typcast ignoriert das Stillschweigend und das Programm läuft weiter, sofern das Object die Eigenschaft trotzdem besitzt.

Darauf würde ich nicht wetten. Wenn du Glück hast, funktioniert's (z.B. bei .Caption oder .Text). Wenn du Pech hast, wirds einfach irgendwo in den Speicher geschrieben und du merkst es vielleicht nicht mal....


FD-83 - Mo 28.02.05 00:59

Also bei Klassen und Interfaces lieber is und as verwenden um Problemen vorzubeugen. So hab ichs bislang auch immer gemacht.

Gruß
Frederik


Motzi - Mo 28.02.05 01:17

Der Operator as prüft intern per is ob das Objekt ein Nachfahre der entsprechenden Klasse ist. Daher sind zB Casts wie

Delphi-Quelltext
1:
2:
 if xyObject is TxyClass then
   (xyObject as TxyClass)...

vollkommen unnötig, da die Überprüfung per is doppelt stattfindet. Hat man bereits selbst per is geprüft kann man ohne Probleme gleich direkt casten: TxyClass(xyObject)

interfaces dürfen nur per as gecastet werden, da dort eine Menge Hintergrundarbeit stattfindet (QueryInterface-Aufruf etc), und Delphi bei einem Typecast mittels as diese komplett transparent durchführt.


IngoD7 - Mo 28.02.05 09:50

Motzi hat folgendes geschrieben:
Der Operator as prüft intern per is ob das Objekt ein Nachfahre der entsprechenden Klasse ist.

Das liest man immer wieder. Ob es so ist, weiß ich nicht. Jedenfalls ist mir der Nutzen einer möglichen Überprüfung in as schleierhaft, denn sie ist nicht mit der Überprüfung mittels is zu vergleichen.

Man darf nämlich nicht den Umkehrschluss ziehen, dass wenn as verwendet wird, dann keine is-Abfrage mehr nötig ist!


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
//Code 1  ------- Kein Crash.
xyObject := nil;
if xyObject is TxyClass then      //is fängt auch nil ab.
  (xyObject as TxyClass).xxx;     //<--- Wird nicht ausgeführt.

//Code 2  ------- Kein Crash.
xyObject := nil;
if xyObject is TxyClass then      //is fängt auch nil ab.
  TxyClass(xyObject).xxx;         //<--- Wird nicht ausgeführt.

//Code 3  ------- Exception!
xyObject := nil;
(xyObject as TxyClass).xxx;       //Wird ausgeführt und fällt auf die Nase.

//Code 4  ------- Exception!
xyObject := nil;
TxyClass(xyObject).xxx;           //Wird ausgeführt und fällt auf die Nase.


Motzi - Mo 28.02.05 10:41

IngoD7 hat folgendes geschrieben:
Das liest man immer wieder. Ob es so ist, weiß ich nicht.

Es ist so, glaub mir.. ;) Wenn man bei einen Cast mittels as per Debugger in der System.pas mitschaut was da passiert findet man sich in einer Funktion "AsClass" wieder, die sowohl eine Pascal, als auch eine ASM Umsetzung hat. Die Pascal-Variante schaut so aus:

Delphi-Quelltext
1:
2:
3:
4:
5:
begin
  Result := Child;
  if not (Child is Parent) then
    Error(reInvalidCast);   // loses return address
end;


Was deinen Fall 3 betrifft:

Delphi-Quelltext
1:
2:
3:
//Code 3  ------- Exception!
xyObject := nil;
(xyObject as TxyClass).xxx;       //Wird ausgeführt und fällt auf die Nase.

Wenn man sich vom Pascal-Code der Funktion AsClass ausgeht hast du recht, dann gibt es eine Exception. Die ASM-Umsetzung geht allerdings ein bisschen anders vor, indem sie als erstes überprüft ob die zu castende Objektreferenz nil ist und liefert in diesem Fall keine Exception.


retnyg - Mo 28.02.05 11:11

und wie bringt man dem compiler bei dass er die asm-version aussführt ? ein DEFINE irgendwo ?


IngoD7 - Mo 28.02.05 11:17

Motzi hat folgendes geschrieben:
Was deinen Fall 3 betrifft:

Delphi-Quelltext
1:
2:
3:
//Code 3  ------- Exception!
xyObject := nil;
(xyObject as TxyClass).xxx;       //Wird ausgeführt und fällt auf die Nase.

Wenn man sich vom Pascal-Code der Funktion AsClass ausgeht hast du recht, dann gibt es eine Exception. Die ASM-Umsetzung geht allerdings ein bisschen anders vor, indem sie als erstes überprüft ob die zu castende Objektreferenz nil ist und liefert in diesem Fall keine Exception.
Was soll mir das mit der ASM-Umsetzung nun sagen? Delphi jedenfalls geht dann wohl vom Pascal-Code aus, denn die Exception kommt.


Motzi - Mo 28.02.05 11:41

Die Pascal und ASM-Umsetzung werden durch Compiler-Schalter getrennt:

Delphi-Quelltext
1:
2:
3:
4:
5:
{$IFDEF PUREPASCAL}
  // Pascal-Umsetzung
{$ELSE}
  // ASM-Umsetzung
{$ENDIF}

Da das Symbol PUREPASCAL aber nicht definiert ist wird die ASM-Umsetzung ausgeführt... (so ist zumindest die Situation in meinem D6 Prof)


IngoD7 - Mo 28.02.05 11:52

Zitat:
Da das Symbol PUREPASCAL aber nicht definiert ist wird die ASM-Umsetzung ausgeführt... (so ist zumindest die Situation in meinem D6 Prof)

Nun mal Hand auf's Herz: :wink:

Folgendes führt bei dir also nicht zur Exception???

Delphi-Quelltext
1:
2:
3:
4:
var Komp : TComponent;
...
Komp := nil;
(Komp as TEdit).Clear;


Motzi - Mo 28.02.05 12:04

Genau so schauts aus..! ;) Extra getestet und mit dem Debugger Schritt für Schritt durchgegangen um zu sehen was da passiert. Hab mir auch mal meine eigene AsClass-Funktion mit dem PurePascal-Code gemacht und die hat (logischerweise) schon zu einer Exception geführt.


delfiphan - Mo 28.02.05 12:09

Purepascal müsste doch genau dasselbe tun wie die asm Version, einfach nicht mit der gleichen Effizienz. Kann doch nicht sein, dass die sich anders verhalten?!? Ich zitiere aus der Hilfe:
"At runtime, object must be (...), or be nil; otherwise an exception is raised."
Da steht nix davon, dass bei nil auch ne Exception geraised wird...
(Ich glaub es euch natürlich schon, finde es einfach ein wenig merkwürdig)


IngoD7 - Mo 28.02.05 12:19

Motzi hat folgendes geschrieben:
Genau so schauts aus..! ;)
Das'n Ding. Habe D7E. Da wirft er die Exception. Ich weiß aber nicht, welchen Teil er ausführt, da ich mich mit dem Debugger nicht so gut auskenne. Eine "Extra-PurePascal-Definition" kann ich jedenfalls nirgends erkennen.


Motzi - Mo 28.02.05 12:29

Aktivier mal in den Projektoptionen unter Compiler "Use Debug DCUs" oder eben das deutsche Äquivalent, erstell das Projekt neu, dann mach nen Breakpoint und geh alles mit F7 durch...


IngoD7 - Mo 28.02.05 12:45

Motzi hat folgendes geschrieben:
Aktivier mal in den Projektoptionen unter Compiler "Use Debug DCUs" oder eben das deutsche Äquivalent, erstell das Projekt neu, dann mach nen Breakpoint und geh alles mit F7 durch...

Habe ich gemacht. Er springt auch bei mir in den ASM-Teil, kommt aber nicht weit:

Quelltext
1:
2:
        TEST    EAX,EAX
        JE      @@exit
Den Sprung führt er schon aus und macht dann in anderen (ASM-)Routinen weiter. Eine Exception wirft er später trotzdem. Mein Wissen reicht allerdings nicht aus, um nun zu verfolgen, wo und weshalb genau.

Bleibt festzustellen, dass as sich durch die Delphiversionen hindurch unterschiedlich verhält. :( Oder aber andere Einstellungen in den Optionen bewirken das ...?


Motzi - Mo 28.02.05 12:58

Genau dieser ASM Code den du gepostet hast ist dafür verantwortlich, dass der erste Parameter auf nil getestet wird und in diesem Fall die Funktion vorzeitig verlassen wird (Result ist dann auch nil). Eine Mögliche Ursache für deine Exception ist, dass das Ergebnis von AsClass eben nil ist und die Methode des Objekts dann eben fehlschlägt und eine Exception auslöst (was für eine Exception bekommst du denn)? Ich bin nämlich davon ausgegangen, dass du eine "Invalid typecast"-Exception bekommst, diese wird aber nur von der PurePascal-Version ausgelöst...

Ich hab das ganze mit folgendem Code getestet:

Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TForm1.Button2Click(Sender: TObject);
begin
  Sender := nil;
  (Sender as TButton).Caption := 'xxx';
end;


BenBE - Mo 28.02.05 13:02

Da das in meinen Augen hier noch keiner komplett richtig erklärt hat, möchte ich das mal tun:

TButton(X) castet X unabhängg von seinem Typ (Also auch Integer und ähnliches) O=HNE Typprüfung.

Bei as TButton wird eine Typprüfung zur Laufzeit durchgeführt, was zwar sicherer ist, aber auch um einiges Langsamer.


IngoD7 - Mo 28.02.05 13:28

Motzi hat folgendes geschrieben:
Eine Mögliche Ursache für deine Exception ist, dass das Ergebnis von AsClass eben nil ist und die Methode des Objekts dann eben fehlschlägt und eine Exception auslöst (was für eine Exception bekommst du denn)?
Ich bekomme eine EAccessViolation. Das tut aber nichts zur Sache, da ich grundsätzlich von Exceptions gesprochen hatte, die "irgendwie" dadurch ausgelöst werden, dass das Objekt nil ist. Eine is-Abfrage verhindert deren Auftreten. Eine as-Nutzung tut das nicht.
Dass es hier ursächlich eine Methode ist, die fehlschlägt (mit besagter EAV), während das Setzen einer Eigenschaft zu keinem Fehler führt, ist mir dabei eigentlich egal. :)

Motzi hat folgendes geschrieben:
Ich hab das ganze mit folgendem Code getestet:

Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TForm1.Button2Click(Sender: TObject);
begin
  Sender := nil;
  (Sender as TButton).Caption := 'xxx';
end;

Moooooment! Jetzt muss ich aber schimpfen mit dir! :evil: :wink:
Explizit gefragt hatte ich dich:
IngoD7 hat folgendes geschrieben:
Folgendes führt bei dir also nicht zur Exception???

Delphi-Quelltext
1:
2:
3:
4:
var Komp : TComponent;
...
Komp := nil;
(Komp as TEdit).Clear;


Deine Antwort war:
Motzi hat folgendes geschrieben:
Genau so schauts aus..! ;)

Das war dann wohl leicht gelogen, oder? 8)

Abgesehen davon: Ein(Sender as TButton).Caption := 'xxx';löst bei mir ebensowenig einen Fehler aus wieTButton(Sender).Caption := 'xxx';.
Beides geht glatt. Dafür braucht man kein as.


Motzi - Mo 28.02.05 13:58

So.. dann lösen wir hier mal ein Missverständnis auf.. ;)
Ich bin davon ausgegangen, dass du eine "Invalid typecast"-Exception meinst, welche in der PurePascal-Version ausgelöst wird, wenn das Objekt entweder nil ist oder kein Nachfahre der angegebenen Klasse. In der ASM-Version wird diese Exception nur ausgelöst, wenn das Objekt kein Nachfahre der Klasse ist, aber _nicht_ wenn das Objekt nil ist.

Ich bin also wie gesagt von einer Exception ausgegangen, die innerhalb des as-Castings ausgelöst wird. Deine EAV ist ja eher ein Nachfolgefehler... da ich diese Möglichkeit nicht in Betracht gezogen habe, hab ich es auch nicht mit deinem Code so genau genommen und statt deinem Code eben einen anderen Code genommen, bei dem auch ein nil-Objekt per as gecastet wird. Dass dabei bei der Zuweisung der Caption kein EAV auftritt war wohl ein "Glücksgriff".. ;) Du mögest mir bitte verzeihen..! :flehan: ;)


IngoD7 - Mo 28.02.05 14:25

Und somit hier mal einen kleinen Zwischenstand:



Zur Verdeutlichung:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
//Code A:
var xyObject : TEdit;
...
  (xyObject as TButton).hide;  //<--- EInvalidCast !

//Code B:
var xyObject : TEdit;
...
  TButton(xyObject).hide;  //Macht kalt lächelnd das Edit unsichtbar.


Motzi - Mo 28.02.05 16:26

Sehr schön zusammengefasst! *thumbsup* Damit sollten hoffentlich alle Klarheiten beseitigt sein.. :mrgreen: ;)


IngoD7 - Mo 28.02.05 17:30

Motzi hat folgendes geschrieben:
Sehr schön zusammengefasst! *thumbsup*

Danke, danke. *verbeug nach links, verbeug nach rechts*
:mrgreen:


FD-83 - Mi 02.03.05 03:21

Wunderschön!

Ich danke euch ;)

Gruß
Frederik


wdbee - Mi 02.03.05 19:15

Beide Formen der Typumwandlung (Cast) haben ihre Daseinsberechtigung! :!:

Der wesentliche Unterschied zwischen


Delphi-Quelltext
1:
2:
  DerSender := TMeinTyp(Sender);
  ...


und


Delphi-Quelltext
1:
2:
3:
4:
5:
  if Sender <> nil then
  begin
    DerSender := Sender As TMeinTyp;
    ...
  end;


ist der, dass in der ersten Form der Compiler schon beim Übersetzen unterstellt, dass es sich bei Sender um ein Objekt der Klasse TMeinTyp handelt. Er wendet also "blind" alles an, was er über die Klasse TMeinTyp weiß. Hier können ganz gemeine Fehler entstehen, weil die Zugriffe auf Daten formal zulässig sind, also keine Exception auslösen, wenn das "falsche" Objekt nicht kleiner ist als das angenommene "richtige" Objekt (bzw. Typ). Die Probleme treten dann an ganz anderen Stellen zu Tage und sind schwer auf die tatsächliche Ursache zurückzuführen, denn selbst der Debugger geht ja vom falschen Typ aus und zeigt alle so an, wir ihr es an dieser Stelle erwartet.

In der zweiten Form wird erst zur Laufzeit geprüft, ob es sich tatsächlich um ein Objekt der angegebenen Klasse handelt (was nur geht, wenn der Zeiger Sender gültig ist!). Die zweite Form ist damit sicher aber langsam.

Wann also darf ich die unsichere schnelle Variante einsezten? :?:

Wenn ihr hier zwischen öffentlichen (public) und nichtöffentlichen (protected, private) Prozeduren oder Funktionen sauber unterscheidet, dann gilt:

In öffentlichen Funktionen müsst ihr mit allem rechnen (Ungültige Zeiger, falsche Typen) und müsst deshalb immer die langsamere aber sichere Variante wählen.

Funktionen, die nur aus der Klasse selbst heraus aufgerufen werden können, dürfen unterstellen, das der Aufrufer weiß was ihr erwartet! Hier könnt ihr deshalb entscheiden, ob ihr die schnellere Variante wählen könnt. Wird eine Funktion also z.B. immer nur von ein und der selben Funktion aufgerufen und hat der Parameter (hier Sender) deshalb immer den gleichen Typ, dann merkt ihr einen eventuellen Denkfehler in eurem Programm beim Testen.

Das Prinzip ist also, dass der Erste, der ein Objekt von außen bekommt alles prüft, und alle anderen sich darauf verlassen können müssen! :idea:

Ein Beispiel für einen gefährlichen Trugschluss haben wir hier:

IngoD7 hat folgendes geschrieben:
Und somit hier mal einen kleinen Zwischenstand:
...

Zur Verdeutlichung:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
//Code A:
var xyObject : TEdit;
...
  (xyObject as TButton).hide;  //<--- EInvalidCast !

//Code B:
var xyObject : TEdit;
...
  TButton(xyObject).hide;  //Macht kalt lächelnd das Edit unsichtbar.


xyObject ist als TEdit deklariert und wird als TButton verwendet. Das geht hier, weil die Verwendung im Aufruf Hide besteht. Diese Methode wird aber weder in TEdit noch in TButton deklariert, sondern in TControl, also einer gemeinsamen Basisklasse! Deshalb führt der Aufruf zum "erwarteten" Ergebnis.

Im allgemeinen gilt das so nicht und die Ergebnisse sind unberechenbar!!!

Moderiert von user profile iconraziel: Code- durch Delphi-Tags ersetzt.
Moderiert von user profile iconraziel: Beiträge zusammengefasst.


retnyg - Do 03.03.05 02:43

respect @ wdbee
hoffentlich gibts hier in zukunft noch öfter was von dir zu lesen.


IngoD7 - Di 08.03.05 01:29

user profile iconwdbee hat folgendes geschrieben:

Ein Beispiel für einen gefährlichen Trugschluss haben wir hier:

IngoD7 hat folgendes geschrieben:
Und somit hier mal einen kleinen Zwischenstand:
...

Zur Verdeutlichung:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
//Code A:
var xyObject : TEdit;
...
  (xyObject as TButton).hide;  //<--- EInvalidCast !

//Code B:
var xyObject : TEdit;
...
  TButton(xyObject).hide;  //Macht kalt lächelnd das Edit unsichtbar.


xyObject ist als TEdit deklariert und wird als TButton verwendet. Das geht hier, weil die Verwendung im Aufruf Hide besteht. Diese Methode wird aber weder in TEdit noch in TButton deklariert, sondern in TControl, also einer gemeinsamen Basisklasse! Deshalb führt der Aufruf zum "erwarteten" Ergebnis.

Im allgemeinen gilt das so nicht und die Ergebnisse sind unberechenbar!!!

Das Beispiel sollte nicht belegen, dass es zum "erwarteten" Ergebnis führt, sondern dass - im Gegensatz zur Verwendung von as - überhaupt (irgend)ein Ergebnis herauskommen kann, obwohl beide Typen ungleich sind. Es stellt somit keinen Trugschluss dar, sondern vielmehr einen Beleg für die auch von dir angemahnte Unberechenbarkeit. Letztere geht aus dem Kontext der vorherigen Postings m.E. auch schon hervor und sollte ganz bestimmt nicht durch das Beispiel widerlegt werden.


wdbee - Di 08.03.05 06:56

@IngoD7: Du hast Recht. :)

Was mir da wichtig war, ist die Tatsache, dass hier eine gemeine Fehlerquelle lauert. Leite von TObject zwei Klassen ab. Die erste bekommt eine Variable oder Methode die A heißt und dann eine die B heißt. Die zweite bekommt erst eine Variable oder Methode die B heißt und dann eine die A heißt.

Was dann passiert ist: Keine Fehlermeldung, aber A und B werden verwechselt, wenn mit direkter Typumwandlung gearbeitet wird. Also gilt nicht immer A=A und B=B oder wie oben dargestellt, Hide=Hide. Das war es was ich ergänzen wollte.