2010-08-04 6 views
6

Ich habe ein Objekt, das von einem Bruchteil einer Sekunde bis zu ein paar Minuten dauern kann, um zu initialisieren. Der Grund dafür ist, dass der Konstruktor Daten von einem Webservice abfragt, die einige Kilobyte bis einige Megabyte betragen können, und abhängig von der Verbindungsgeschwindigkeit des Benutzers kann die Leistung stark variieren. Aus diesem Grund möchte ich Ereignisse in die Fortschrittsbenachrichtigung einfügen.Event Handlers in Konstruktoren - ist es möglich oder sogar klug?

Hier ist meine Frage: Kann ich Handler im Konstruktor Ereignis setzen oder sollte diese Art der Aktion mit einer Load-Methode durchgeführt werden?

Zum Beispiel:

public class MyObject 
{  
public event EventHandler<UpdateLoadProgressEventArgs> UpdateLoadProgress;  

public MyObject(int id) 
{ 
    Background worker bgWorker = new BackgroundWorker(); 
    bgWorker.DoWork += delegate(object s, DoWorkEventArgs args) 
    { 
     //load data and update progress incrementally 
     UpdateLoadProgress(this, new UpadteLoadProgressEventArgs(progressValue)); 

     Result = someValue;   
    } 
    bgWorker.RunWorkAsync(); 

} 

public int Result 
{ 
    get; 
    set; 
} 

} 

aber wenn ich versuche, die Event-Handler an den Konstruktor zu binden sie sind immer null, wenn sie aufgerufen werden:

MyObject o = new MyObject(1); 
o.UpdateLoadProgress += new EventHandler<EventArgs>(o_UpdateLoadProgress); 

Ich nehme an, dies geschieht, weil ich Draht up die Ereignisse nach dem Konstruktor. Die einzige Alternative, die ich sehe, ist das Erstellen einer Load-Methode, die die Arbeit des Konstruktors ausführt. Der Nachteil ist, dass jeder, der diese Klasse verwendet, Load aufrufen muss, bevor er auf Result (oder eine andere Eigenschaft) zugreifen kann.

EDIT: Hier ist die endgültige Lösung:

MyObjectBuilder Klasse

public class MyObjectBuilder 
    { 
     public event ProgressChangedEventHandler ProgressChanged; 

     public MyObject CreateMyObject() 
     { 
      MyObject o = new MyObject(); 
      o.Load(ProgressChanged); 

      return o; 
     } 
    } 

MyObject Klasse

public class MyObject 
    { 
     public int Result { get; set;} 

     public void Load(ProgressChangedEventHandler handler) 
     { 
      BackgroundWorker bgWorker = new BackgroundWorker(); 
      bgWorker.WorkerReportsProgress = true; 
      bgWorker.ProgressChanged += handler; 

      bgWorker.DoWork += delegate(object s, DoWorkEventArgs args) 
      { 
       for (int i = 0; i < 100; i++) 
       { 
        Thread.Sleep(10); 
        Result = i; 

        bgWorker.ReportProgress(i); 
       } 
      }; 
      bgWorker.RunWorkerAsync();      
     } 
    } 

Programm Klasse

class Program 
    { 
     static void Main(string[] args) 
     { 
      MyObjectBuilder builder = new MyObjectBuilder(); 
      builder.ProgressChanged += new ProgressChangedEventHandler(builder_ProgressChanged);   

      MyObject o = builder.CreateMyObject(); 
      Console.ReadLine(); 
     } 

     static void builder_ProgressChanged(object sender, ProgressChangedEventArgs e) 
     { 
      Console.WriteLine(e.ProgressPercentage); 
     } 
    } 
+1

Ich habe im Allgemeinen festgestellt, dass es eine schlechte Idee für einen Konstruktor ist, Daten zu laden. Sie werden es früher oder später bereuen - und wenn Sie Komponententests durchführen, wird es "früher" sein. –

Antwort

8

Eine andere Möglichkeit wäre, die Event-Handler in den Konstruktor zu übergeben, natürlich.

Ich persönlich versuche zu vermeiden, so etwas in einem Konstruktor zu tun. Das Erstellen eines neuen Objekts sollte im Allgemeinen keine Hintergrundaufgaben, IMO, starten. Vielleicht möchten Sie es stattdessen in eine statische Methode einfügen, die natürlich einen privaten Konstruktor aufrufen kann.

Sie können Ihre Klasse auch in zwei Teile aufteilen - einen Builder, der alles vorbereitet (z. B. die Ereignisse) und dann eine Klasse "in-flight or completed" mit der Eigenschaft Result. Sie würden Start oder etwas ähnliches in der ersten Klasse anrufen, um eine Instanz der Sekunde zu erhalten.

+0

@Jon Ich versuchte einen Stich auf Ihre Idee, eine Builder-Klasse zu verwenden. Ich glaube nicht, dass ich das Konzept verstehe.Ich übergebe den Event-Handler an MyObjectBuilder und gebe dann ein MyObject mit einer create-Methode zurück. Leider ist MyObject immer noch nicht vollständig geladen. Irgendwelche Verbesserungen, die Sie an meinem obigen Code vornehmen würden? –

+0

@Blake: Nun, ich würde das Ereignis nicht als Teil von MyObject haben - ich würde einfach den Handler in den Konstruktor übergeben (aus MyObjectBuilder.CreateMyObject). Das sieht aber so aus, als müsste es grundsätzlich funktionieren ... was ist los? (Beachten Sie, dass Sie * beide * Ansätze in diesem Code mischen. Wenn Sie zufrieden sind, dass der Beispielcode den Ereignishandler zu Beginn übergibt, können Sie alles in einem Typ ausführen, vielleicht mit einer statischen Methode Die Idee des Builders war, dass Sie es konstruieren, die Event-Handler hinzufügen und * dann * die Create-Methode aufrufen.) –

+0

@Jon Das Hauptproblem ist, dass an der Stelle von Console.WriteLine (o.Result) die Ausgabe 0 ist anstelle der erwarteten 99 (nachdem die Schleife abgeschlossen ist). Der Fortschritt wird korrekt aktualisiert, nur dass das Objekt nicht vollständig initialisiert ist. Ist das Objekt vollständig initialisiert ein Design-Ziel, das ich haben sollte, oder wird das vom Builder-Muster verstanden, dass es noch im Aufbau ist? –

1

Ist das möglich? Könnte sein. Ist es klug?

Der offensichtlichste Grund ist, dass Sie nicht garantieren können, dass der Hintergrundthread nicht ausgeführt wird, bevor die Ereignisprozeduren nach der Konstruktion des Objekts verdrahtet werden. Oder schlimmer noch, einige der Event-Handler können abonniert werden, während andere nicht.

Verwandte Themen