Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Prism Interfaces in DotNet ganz anders? (https://www.delphipraxis.net/35043-interfaces-dotnet-ganz-anders.html)

Fingolfin 1. Dez 2004 19:33


Interfaces in DotNet ganz anders?
 
Hi, ich arbeite mich gerade mit Delphi 2005 in DotNet ein und mir ist da etwas aufgestoßen.

Früher (in Win32) hat man interfaces üblicherweise ja so verwendet:

Code:
  ITest = interface
    procedure DoSomething;
  end;

  TTest = class(TInterfacedObject, ITest)
  public
    constructor Create;
    destructor Destroy; override;
    procedure DoSomething;
  end;

var
  FTest: ITest;

procedure InterfaceErzeugen;
begin
  FTest := TTest.Create;
  FTest.DoSomething;
end;

procedure Interfacefreigeben;
begin
  FTest := nil;
end;
Dabei führte das nilen der Interfacereferenz zur Freigabe, es wurde also in diesem Fall das Destroy von TTest aufgerufen.

Unter Delphi 2005 passiert das nicht. Das nilen nilt zwar die Interface-Referenz, der Destructor wird aber überhaupt nicht berührt.

Ich habe jetzt schon Sachen wie

Code:
var
  FTest: TTest;
procedure Irgendwas;
begin
  FTest := TTest.Create;
  FTest.DoSomething;
  FTest.Free;
end;
getestet und die haben anscheinend ohne Probleme funktioniert. Dabei ist doch gerade die Mischung von Object- und Interfacereferenzen das absolut böse "ohoh" in Delphi Win32.

Kann mich da mal jemand aufklären bitte? Bin sehr verwirrt. :cry:

Vielen Dank schonmal.

Ultimator 1. Dez 2004 19:39

Re: Interfaces in DotNet ganz anders?
 
Ich hab mal was gelesen, dass das mit dem free'en was mit der Garbage Collection zu tun hat

sakura 1. Dez 2004 19:41

Re: Interfaces in DotNet ganz anders?
 
Interfaces werden unter .NET durch den Garbage Collector verwaltet. Dieser räumt nur in unregelmäßigen Intervallen auf. D.h. Du kannst den Zeitpunkt nicht mehr direkt bestimmen. Muss wohl mal ein Tutorial her :mrgreen:

...:cat:...

Fingolfin 1. Dez 2004 19:47

Re: Interfaces in DotNet ganz anders?
 
Hmm, es ist ja nicht so, daß ich davon noch nicht gehört hätte. Aber wenn dem so wäre müßte der Garbage Collector doch spätestens collecten, wenn die Applikation geschlossen wird. Da passiert aber gar nix.

Waaah, wie soll ich nur programmieren, wenn ich noch nicht mal Code bei der Freigabe eines Objektes ausführen kann? :wall:

:cry:

Fingolfin 1. Dez 2004 19:59

Re: Interfaces in DotNet ganz anders?
 
Nachtrag:

Nochmal von der anderen Seite. Normale Objekte kann ich doch auch explizit freigeben (Destroy, Disposable usw.). Das ist doch auch definitiv wichtig, wenn die z.B. die Freigabe eines Objekts andere Objekte über dessen Tod benachrichtigen soll (Bsp.: Katze, du brauchst keine Mäuse mehr jagen, die sind alle am Rattengift gestorben). Ich kann mir einfach nicht vorstellen, daß Interfaces hier derart aus der Rolle fallen. Das würde ihren Wert doch massiv schmälern.

Vielleicht ist mein zweiter Ansatz (Mischung von Objekt- und Interfacereferenzen) doch naheliegender. Kann es sein, daß eine solche Herangehensweise jetzt erzwungen wird?

Hmm, vielleicht kann ja noch jemand Licht in das Dunkel bringen.

Auf jeden Fall schonmal vielen Dank.

mirage228 1. Dez 2004 20:06

Re: Interfaces in DotNet ganz anders?
 
Du könntest versuchen mit

System.GC.Collect();

den Garbage Collector zu zwingen, aufzuräumen. Vielleicht wird Dein Destruktor ja dann aufgerufen.

mfG
mirage228

Fingolfin 1. Dez 2004 20:14

Re: Interfaces in DotNet ganz anders?
 
Zitat:

Zitat von mirage228
Du könntest versuchen mit

System.GC.Collect();

den Garbage Collector zu zwingen, aufzuräumen. Vielleicht wird Dein Destruktor ja dann aufgerufen.

Danke für den Tipp, aber da regt sich auch nichts. Hier scheint was sehr grundlegend anders als bei Win32 zu laufen. Und ich dachte DotNet mal 2-3 Stunden angucken und gut. ;)

mirage228 2. Dez 2004 05:57

Re: Interfaces in DotNet ganz anders?
 
Zitat:

Zitat von Fingolfin
Zitat:

Zitat von mirage228
Du könntest versuchen mit

System.GC.Collect();

den Garbage Collector zu zwingen, aufzuräumen. Vielleicht wird Dein Destruktor ja dann aufgerufen.

Danke für den Tipp, aber da regt sich auch nichts. Hier scheint was sehr grundlegend anders als bei Win32 zu laufen. Und ich dachte DotNet mal 2-3 Stunden angucken und gut. ;)

Hi,

habe gleich noch einen :mrgreen:

Habe ich in einem anderen Thread gefunden:
Delphi-Quellcode:
GC.WaitforPendingFinalizers();
mit dem Kommentar "wenn es ganz haarig wird" :)

Vielleicht hilft Dir das ja.

mfG
mirage228

merlin17 2. Dez 2004 08:04

Re: Interfaces in DotNet ganz anders?
 
Hallo,

Zitat:

Hier scheint was sehr grundlegend anders als bei Win32 zu laufen. Und ich dachte DotNet mal 2-3 Stunden angucken und gut. Wink
DER war richtig gut, da fängt der Arbeitstag mit einem richtigen Schmunzeln an :spin2:

Ich habe das erstemal am 17.05.2001 (musste ich im Regal jetzt ganz schnell suchen) mit .net zu tun gehabt und schon dort war klar:
mit den .net E-Businesslösungen (so hies damals das 1st-Training) ist alles neu.... ohne Altlasten....



:-) thomas


Sorry, normal bin ich nicht so schnell OT, aber hier kann ich es mir nicht verkneifen....

sakura 2. Dez 2004 17:00

Re: Interfaces in DotNet ganz anders?
 
Ich habe mal ein erstes Tutorial zu diesem Thema verfasst: Der .NET Garbage Collector

...:cat:...

Fingolfin 2. Dez 2004 20:18

Re: Interfaces in DotNet ganz anders?
 
Zitat:

Zitat:

Hier scheint was sehr grundlegend anders als bei Win32 zu laufen. Und ich dachte DotNet mal 2-3 Stunden angucken und gut.
DER war richtig gut, da fängt der Arbeitstag mit einem richtigen Schmunzeln an Spin.
:) Ich glaube, ich hätte besser den hinmachen sollen: :roll:


:) Danke für das Tutorial Sakura. Das klärt einiges auf und ich denke die Grundzüge verstanden zu haben. :)

Nichtsdestotrotz wundern mich 3 Kleinigkeiten dabei:

1) Kann ich dem GC trauen?
Mal ehrlich. Nehmen wir mal an ich habe ein Objekt auf das an einem spezifischen Punkt des Programms nicht mehr zugegriffen wird (z.B. alle Buttons, deren Klick eine Methode des Objekt aufrufen können, werden bis zum Neustart der Applikation disabled). Wie kann der GC solche Sachen auch nur ansatzweise nachvollziehen? Klappt sowas wirklich auch bei komplexeren Applikationen reibungslos?

2) Performance
Ich mache hauptsächlich Multimedia-Anwendungen mit einer gehörigen Menge beweglicher Grafik. Insofern kann es manchmal zu nicht unerheblichen Performance-Einbrüchen kommen, wenn die Bilddaten nicht definiert und regelmäßig freigegeben werden, sondern sich der GC irgendwann mal dazu bequemt, diese Objkte freizugeben.

3) OO-Design
Wie ich schon vorher erwähnte, gibt es doch viele Momente in denen die Freigabe eines Objekts andere Objekte darüber benachrichtigt, damit diese irgendein Verhalten ausführen können. Um das zu bewerkstelligen, müßte ich ja jetzt wieder den expliziten Destruktor-Aufruf erzwingen und das ist gerade nicht im Sinne von DotNet (wie ich deinem Tut entnehmen kann ;) ). Ist das jetzt schlechter OOP-Stil von mir oder kann man das Problem irgendwie andersweitig umgehen?

Vielen Dank schonmal. :)

Robert_G 2. Dez 2004 21:29

Re: Interfaces in DotNet ganz anders?
 
"Thread titel
Interfaces in DotNet ganz anders?
Zum Glück auch. ;)

Zitat:

Zitat von Fingolfin
1) Kann ich dem GC trauen?

Solange du nicht wie ein VB'ler wild drauf los erzeugst ist alles in Butter. :mrgreen:

Zitat:

Zitat von Fingolfin
2) PerformanceIch mache hauptsächlich Multimedia-Anwendungen mit einer gehörigen Menge beweglicher Grafik. Insofern kann es manchmal zu nicht unerheblichen Performance-Einbrüchen kommen, wenn die Bilddaten nicht definiert und regelmäßig freigegeben werden, sondern sich der GC irgendwann mal dazu bequemt, diese Objkte freizugeben.

Und du bist dir sicher, dass du da mit D32 besser dran warst? In Delphi32 gibt es einen messbaren Performance hit bei der Verwendung von Interfaces. Außerdem

Zitat:

Zitat von Fingolfin
3) OO-Design
Wie ich schon vorher erwähnte, gibt es doch viele Momente in denen die Freigabe eines Objekts andere Objekte darüber benachrichtigt, damit diese irgendein Verhalten ausführen können.

Was hindert dich daran einen Event im Finalizer oder in der Dispose zu feuern? :gruebel:


Warum bin ich froh, dass Interfaces in .Net mit denen aus D32 nicht viel gemeinsam haben?
Hast du schonmal versucht die Klasse hinter einer Interfaceinstanz abzufragen?
Ich kenne nur dieses scheußliche Workaround. :?
Delphi-Quellcode:
type ISomeInterface = interface
   procedure Allright();
   procedure Hmmm();
   // scheußliches Workaround :[
   function ComparableInstance() :TObject;
end;

type TSomeImplementingClass = class(TInterfacedObject,ISomeInterface)
   public
      procedure Allright();
      procedure Hmmm();
      function ComparableInstance() :TObject;
end;

{ TSomeImplementingClass }
procedure TSomeImplementingClass.Allright();
begin
   Writeln('OK :thuimb:');
end;

procedure TSomeImplementingClass.Hmmm();
begin
   Writeln(':gruebel: Hmmm...');
end;

function TSomeImplementingClass.ComparableInstance(): TObject;
begin
   Result := Self;
end;

var
   NewInstance :ISomeInterface;
begin
   NewInstance := TSomeImplementingClass.Create();

   if NewInstance.ComparableInstance() is TSomeImplementingClass then
      NewInstance.Allright()
   else
      NewInstance.Hmmm();

   Sleep(3000);
end.

Da du in .Net die Referenzzählung nicht mehr selbst machen muss, sieht es auch gleich viel "aufgeräumter" aus. :mrgreen:
Code:
   public interface ISomeInterface
   {
      void Allright();
      void Hmmm();
   }

   public class SomeImplementingClass : ISomeInterface
   {
      public void Allright()
      {
         Console.WriteLine("OK :thuimb:");
      }

      public void Hmmm()
      {
         Console.WriteLine(":gruebel: Hmmm...");
      }
   }

   internal class Program
   {
      private static void Main(string[] args)
      {
         ISomeInterface NewInstance = new SomeImplementingClass();

         if (NewInstance is SomeImplementingClass)
            NewInstance.Allright();
         else
            NewInstance.Hmmm();

         Thread.Sleep(3000);
      }
   }
}
Du kannst wie gehabt den is-OIperator benutzen. (verhält sich schließlich wie eine Objektreferenz :) )

Edits: Schlechtschreibung...

stoxx 3. Dez 2004 02:37

Re: Interfaces in DotNet ganz anders?
 
Hallo Sakura,

noch eine kleine Frage zu dem Tutorial.
Wenn ich Destroy (free) weiterhin benutze.

Heisst das, Destroy wird aufgerufen, die dortigen Anweisungen, wie das Freigeben von Resourcen (Bitmaps) wird ausgeführt.

Ist dann das Objekt SELBER auch freigegeben oder nicht ?
Ist das verhalten so wie früher oder wird das Objekt selber vom GC später irgendwann freigegeben ?

sakura 3. Dez 2004 09:33

Re: Interfaces in DotNet ganz anders?
 
Schritt für Schitt. Erst einmal Fingolfin...

Zitat:

Zitat von Fingolfin
1) Kann ich dem GC trauen?

Ja :-)

Zitat:

Zitat von Fingolfin
z.B. alle Buttons, deren Klick eine Methode des Objekt aufrufen können, werden bis zum Neustart der Applikation disabled

Get natürlich nicht. Woher soll .NET wissen, dass Du nicht doch mal irgendwann wieder die Buttons enablest <g>? Also einfach die Referenzen löschen und fertig ;-)

Zitat:

Zitat von Fingolfin
Ich mache hauptsächlich Multimedia-Anwendungen mit einer gehörigen Menge beweglicher Grafik.

Nutze IDisposable.

Zitat:

Zitat von Fingolfin
andere Objekte darüber benachrichtigt, damit diese irgendein Verhalten ausführen können.

Nutze IDisposable.

Dann kannst Du das Objekt zwingen aufzuräumen und evtl. andere Objekte benarchichtigen. Unter .NET vergiss einfach, dass es einen Destructor gibt, der wird weder von der GC aufgerufen noch ist der den VBlern bekannt, die setzen alle auf Finalize/IDisposable. Mehr dazu in einem weiteren Tutorial dieser Tage.

...:cat:...

Fingolfin 3. Dez 2004 23:00

Re: Interfaces in DotNet ganz anders?
 
Danke erstmal für die vielen Kommentare. :)

Zitat:

Zitat von Robert_G
Zitat:

Zitat von Fingolfin
1) Kann ich dem GC trauen?

Solange du nicht wie ein VB'ler wild drauf los erzeugst ist alles in Butter. :mrgreen:

und

Zitat:

Zitat von sakura
Ja

Nunja, seid nicht sauer, aber ich hatte auf eine etwas technischer belegte Antwort gehofft. Aber ich nehme mal an, eure Überzeugung ist erfahrungsbedingt, also werdet Ihr damit sicher recht haben.

Zitat:

Zitat von Robert_G
Zitat:

Zitat von Fingolfin
2) PerformanceIch mache hauptsächlich Multimedia-Anwendungen mit einer gehörigen Menge beweglicher Grafik. Insofern kann es manchmal zu nicht unerheblichen Performance-Einbrüchen kommen, wenn die Bilddaten nicht definiert und regelmäßig freigegeben werden, sondern sich der GC irgendwann mal dazu bequemt, diese Objkte freizugeben.

Und du bist dir sicher, dass du da mit D32 besser dran warst? In Delphi32 gibt es einen messbaren Performance hit bei der Verwendung von Interfaces. Außerdem

Nun, ein kleine Unterschied besteht schon zwischen ein paar Interfaces und 100 MB oder mehr Grafik im Speicher. ;)

Zitat:

Zitat von Robert_G
Warum bin ich froh, dass Interfaces in .Net mit denen aus D32 nicht viel gemeinsam haben?
Hast du schonmal versucht die Klasse hinter einer Interfaceinstanz abzufragen?
Ich kenne nur dieses scheußliche Workaround. :?

Ich denke, das ist Ansichtssache. Wenn ich Interfaces benutze baue ich ja gerade auf eine recht starke Abstrahierung. Mit anderen Worten, die Notwendigkeit einen Self-Parameter zu benötigen, sollte meines Erachtens bei Interfaces gar nicht zu Tage treten.

Zitat:

Zitat von Robert_G
Da du in .Net die Referenzzählung nicht mehr selbst machen muss, sieht es auch gleich viel "aufgeräumter" aus. :mrgreen:

Das verwirrt mich jetzt etwas. In Delphi besteht doch im Grunde keine Notwendigkeit selbst an der Referenzzählung rumzuspielen?!? Das käme eigentlich nur bei der Mischung von Interface- und Objektreferenzen zum Tragen und das habe ich immer vermieden, weil es eben so stark fehleranfällig ist.

@Sakura
Nutze...

Danke für die Hinweise.

Aber das ist genau der Knackpunkt den ich meine. Ich habe das Gefühl ab sofort mehr Arbeit darauf zu verwenden, darüber nachzudenken, ob ich denn nun irgendwas in irgendeiner Form freigeben werden kann/muß/soll, als früher, als ich prinzipell alles sofort nach dem Gebrauch gefreet habe.
Der GC mag ja für viele Apps praktisch sein, aber im Arbeitsalltag kommen doch gerade diese kleinen "Sonderwünsche" andauernd vor.

Ich möchte aber auch nicht zu negativ klingen. Im Allgemeinen gefällt mir DotNet ganz gut. Vielleicht fehlt mir einfach noch der technische Sachverstand, um die Vorteile des GC zu sehen. (Wenn jemand die vielleicht mal in einem Satz niederschreiben könnte, bezugnehmend auf meine Verwunderung, wäre ich überglücklich :) ).

Danke auf jeden Fall für die Kommentare. :)

Robert_G 4. Dez 2004 00:28

Re: Interfaces in DotNet ganz anders?
 
Zitat:

Zitat von Fingolfin
Zitat:

Zitat von Robert_G
Da du in .Net die Referenzzählung nicht mehr selbst machen muss, sieht es auch gleich viel "aufgeräumter" aus. :mrgreen:

Das verwirrt mich jetzt etwas. In Delphi besteht doch im Grunde keine Notwendigkeit selbst an der Referenzzählung rumzuspielen?!? Das käme eigentlich nur bei der Mischung von Interface- und Objektreferenzen zum Tragen und das habe ich immer vermieden, weil es eben so stark fehleranfällig ist.

Du hast dich selbst beantwortet. ;)
Mit TInterfacedObject bekommst das Standardverhalten frei Haus. Aber wenn du dieses Objekt einmal als Interface benutzt kann es dir sehr schnell unter'm Hintern weggeklaut (==zerstört) werden.
Wenn man _Release selbst implementiert kann man es verhindern, aber da sich die Instanz selbst verwalten muss kann es ganz schön haarsträubend ausarten. :?

Zum Thema GC & Vertrauen:
Die GC funktioniert normalerweise absolut prima, aber es gibt Grenzfälle...
  • Die GC interessiert sich überhaupt nicht für unmanaged resources und ob diese eine Instanz einer managed Klasse referenzieren.
    Sie wird dir deshalb diese Instanz wegklauen, wenn sie von keiner anderen managed Instanz referenziert wird oder du ihr Leben nicht verlängerst.
    • Zum Beispiel mit GC.SupressFinalize/GC.ReRegisterForFinalize kannst du verhindern, dass die Instanz vorzeitig zerstört wird:
      Delphi-Quellcode:
      //verhindern, dass die Instanz zerstört wird
      GC.SupressFinalize(EineInstanz);
      //mache irgendwas...
      GC.ReRegisterForFinalize(EineInstanz);
      // ab jetzt kann die Instanz wieder von der GC zerstört werden
    • Sehr simpel ist KeepAlive, aber man kann es sicher nicht überall verwenden.
      Wenn du in einer Methode das da schreibst:
      Delphi-Quellcode:
      GC.KeepAlive(EineInstanz);
      Dann weiß die GC, dass du mindestens bis zu diesem Punkt eine Referenz auf die Instanz bewahren willst.

      Delphi-Quellcode:
      SomeClass EineInstanz= new SomeClass();
      //irgend ein unmanaged call, der zum Bleistift EineInstanz als Parameter braucht...
      //und hier gleich noch einer...
      //ohne GC.KeepAlive könnte EineInstanz schon nach/während des ersten Calls tot sein
      GC.KeepAlive(EineInstanz);
      // ab jetzt kann die GC diese Instanz zerstören

  • Die GC läuft in allen Nicht-Service-Applications (hübsches Wort :mrgreen: ) als Hintergrundthread.
    d.h.: Du hast gerade mit deinen 100MB an Sprites gespielt, nun machst du etwas anderes und erwartest, dass die GC aufräumt...
    Dumm ist nur, dass "etwas anderes" die CPU ziemlich beansprucht, "noch etwas anderes" die 2. CPU, ... außerdem hat dein System noch genügend RAM frei.
    Die GC sieht jetzt gar nicht ein warum sie der armen CPU den Stress des Aufräumens antun sollte. -> Sie wird also nicht aufräumen.
    Du weißt aber, dass du den Speicher gleich brauchen wirst. (Hellsehen ist eines der Dinge, die M$ noch nicht in die GC integriert hat -> kommt wohl in .Net 0815 :mrgreen: )
    Deshalb kannst du die GC mit:
    Delphi-Quellcode:
    GC.Collect();
    ... dazu anweisen, zu sortieren.
    Wenn du erst dann weitermachen willst, wenn der Speicher frei ist:
    Delphi-Quellcode:
    GC.WaitForPendingFinalizers();
    Dadurch wird dein Thread pausiert bis alle Instanzen, die von der GC als NullReferenzen erkannt wurden, weg sind.
    Damit gehst du sicher, dass der Speicher frei ist, aber du hast kurzeitig den Performancegewinn durch einen automen "Aufräum-thread" aufgegeben.
    Diesen, etwas krassen Schritt, habe ich bisher immer wieder aus meinen Code entfernt, da es (nach ein paar Tests) einfach nicht notwendig war. ;)
Zitat:

Zitat von Fingolfin
Wenn jemand die vielleicht mal in einem Satz niederschreiben könnte, bezugnehmend auf meine Verwunderung, wäre ich überglücklich

:gruebel: War wohl doch mehr als ein Satz...
So jetzt kann es die Mieze zerreißen. :zwinker:


Alle Zeitangaben in WEZ +1. Es ist jetzt 21:30 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