Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Kann operator "as T" fehlschlagen, wenn operator "is T" OK war ? (https://www.delphipraxis.net/211100-kann-operator-t-fehlschlagen-wenn-operator-t-ok-war.html)

Rollo62 28. Jul 2022 11:47

Delphi-Version: 11 Alexandria

Kann operator "as T" fehlschlagen, wenn operator "is T" OK war ?
 
Hallo zusammen,

ich frage mich generell wie weit man sich auf das "is" - "as" Schema verlassen kann.
Ist das für alle möglichen Fälle 100% wasserdicht ( außer Multithread natürlich ), oder
muss man das unter bestimmten Umständen besser in try-except kapseln um Exceptions abzufangen ?

Es geht um ungefähr folgendes Konstrukt, hier mal beispielhaft sowas wie die Übergabe von Sender:

Delphi-Quellcode:
procedure TMyClass.TestParamter( const ASender : TObject );
var
    LBtn1 : TButton;
    LBtn2 : TSpeedButton;
begin
    if not Assigned( ASender ) then
        Exit; //<== GUARD ==============================

    if ( ASender is TButton ) then
    begin
        LBtn1 := ASender as TButton;      // Ist der Zugriff immer Valide, wenn der "is" operator True geliefert hat ?
    end;

    if ( ASender is TSpeedButton ) then
    begin
        LBtn2 := ASender as TSpeedButton; // Ist der Zugriff immer Valide, wenn der "is" operator True geliefert hat ?
    end;

    ...

end;
Die Frage ist: Kann es bestimmte Objekt-Konstruktionen geben, welche bei der Umwandlung "ASender as T;" krachen können ?
Ich blende MultiThread-Zugriffe hier mal aus und dass jemand von außen das Object zerstören kann bevor es benutzt wird.

Und wenn es Problemfälle geben kann, welches wäre dann die problematische Konstellation bei der Klasse ( Generics, InterfacedObject, ... ) ?

Ich vermute die Antwort ist: Ja der Zugriff ist 100% sicher,
oder könnte es doch ein "Aber" geben :stupid:

mkinzler 28. Jul 2022 12:00

AW: Kann operator "as T" fehlschlagen, wenn operator "is T" OK war ?
 
as <Typ> impliziert is <Typ>. Wenn Du diese Überprüfung manuell ausführst kannst Du auch einen harten Cast ausführen.

Delphi-Quellcode:
if ( ASender is TButton ) then
begin
    LBtn1 := TButton(ASender); // Ist der Zugriff immer Valide, wenn der "is" operator True geliefert hat ?
end;

Uwe Raabe 28. Jul 2022 12:05

AW: Kann operator "as T" fehlschlagen, wenn operator "is T" OK war ?
 
Unter der Annahme, dass sich die Instanz zwischen
Delphi-Quellcode:
is
und
Delphi-Quellcode:
as
nicht ändert ist das sicher. Lediglich wenn die Instanz von Sender zwischenzeitlich freigegeben würde und an gleicher Stelle ein anderes Objekt erzeugt wird kann es krachen. Selbst wenn es nicht kracht hat man dann natürlich ein Problem mit einem nicht mehr existenten TButton. Das kann aber nur bei Multithreading passieren und dann muss man das eben anders abfangen. Eigentlich kann aber auch das nicht sein, da das Freigeben eines TButton außerhalb des MainThread eh nicht erlaubt ist.

Die entsprechenden Implementierungen sehen so aus (als Ergänzung zur Antwort von mkinzler):
Delphi-Quellcode:
function _IsClass(const Child: TObject; Parent: TClass): Boolean;
begin
  Result := (Child <> nil) and Child.InheritsFrom(Parent);
end;

function _AsClass(const Child: TObject; Parent: TClass): TObject;
begin
  Result := Child;
  if (Child <> nil) and not (Child is Parent) then
    ErrorAt(Byte(reInvalidCast), ReturnAddress);
end;

himitsu 28. Jul 2022 12:59

AW: Kann operator "as T" fehlschlagen, wenn operator "is T" OK war ?
 
Wenn man das Objekt freigegeben hat (Free),
dann kann IS immernoch ein TRUE liefern, wenn im Speicher immernoch der Rumpf des Objektes existiert (nicht bereits überschrieben wurde).

AS würde dort auch noch funktionieren, aber der Zugriff auf dieses eigentlich "nicht mehr vorhandene" Objekt kann natürlich dennoch knallen.


Aber hier etwas beim Zugriff machen zu wollen ist nahezu unmöglich.
Der Fehler liegt diesbezüglich schon weit vorher, dort wo freigegeben, aber die Variable nicht geNILt wurde.




IS und AS verhalten sich nahezu gleich.
Alles was IS erlaubt, ist dann bei AS auch möglich.

Nur beim NIL verhalten sie sich unterschiedlich.
IS liefert bei NIL ein FALSE, während AS das NIL ohne Fehlermeldung durchlassen würde.

Delphi-Quellcode:
if (ASender is TButton) or not Assigned(ASender) then
begin
  LBtn1 := TButton(ASender); // oder LBtn1 := (ASender as TButton) ... funktioniert immer, aber ist "unnötig", da IS bereits geprüft hatte
end;

Rollo62 28. Jul 2022 14:10

AW: Kann operator "as T" fehlschlagen, wenn operator "is T" OK war ?
 
Das ist gut, danke für die Bestätigungen.

@mkinzler
Das "as" auch "is" macht war mir so im Detail nicht bewusst, aber es macht ja für mich keinen Unterschied wenn ich eine Exception im Vorfeld verhindern möchte.

Der eigentliche Übeltäter ist wohl ErrorAt():
Delphi-Quellcode:
  if (Child <> nil) and not (Child is Parent) then
    ErrorAt(Byte(reInvalidCast), ReturnAddress);
Ich wollte mir da was Generisches bauen, so in der Art, und frage mich immer noch ob es den ganzen Aufwand wert ist:

Delphi-Quellcode:

type   // Safe type cast from TObject to a derived class, if available, w/o crash
    TMyCast<T : class> = class sealed
        class function CanCast( Sender : TObject )                 : Boolean;
        class function TryCast( Sender : TObject; var ACasted : T ) : Boolean;
    end;

...

{ TMyCast<T> }

class function TMyCast<T>.CanCast( Sender : TObject ) : Boolean;
begin
    Result := False;

    if not Assigned( Sender ) then
    begin
        Exit; //<== GUARD ======================================================
    end;

    if not ( Sender is T ) then
    begin
        Exit; //<== GUARD ======================================================
    end;

    Result := True;
end;


class function TMyCast<T>.TryCast( Sender : TObject; var ACasted : T ) : Boolean;
begin
    ACasted := nil;
    Result := False;

    if not Assigned( Sender ) then
    begin
        Exit; //<== GUARD ======================================================
    end;

    if not ( Sender is T ) then
    begin
        Exit; //<== GUARD ======================================================
    end;

    ACasted := Sender as T;
    Result := True;
end;

himitsu 28. Jul 2022 14:50

AW: Kann operator "as T" fehlschlagen, wenn operator "is T" OK war ?
 
Wenn es bei ErrorAt knallt, dann stimmt der Typ in T nicht mit dem im Sender übererin.

Wenn ich alles rauswerfe, was doppelt ist, da IS es bereits macht (GUARD), dann bleibt das übrig:
Delphi-Quellcode:
class function TMyCast<T>.CanCast( Sender : TObject ) : Boolean;
begin
  Result := Sender is T;
end;

class function TMyCast<T>.TryCast( Sender : TObject; var ACasted : T ) : Boolean;
begin
  ACasted := nil;
  Result := Sender is T;
  if Result then
    ACasted := Sender as T; //  oder := T(Sender);
end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 05:34 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz