2010-08-04 10 views
6

Tengo un objeto que puede tomar desde una fracción de segundo a un par de minutos para inicializar. La razón es que el constructor recupera datos de un servicio web que pueden ser de algunos kilobytes a varios megabytes, y dependiendo de la velocidad de conexión del usuario, el rendimiento puede variar mucho. Por esa razón, quiero poner eventos que manejarán la notificación de progreso.Controladores de eventos en constructores: ¿es posible o incluso sabio?

Aquí es mi pregunta: ¿Puedo poner eventos manipuladores en el constructor o debería este tipo de acción puede hacer con un método de carga ?

Por ejemplo:

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; 
} 

} 

Sin embargo cuando intento para atar los controladores de eventos al constructor siempre son null cuando se llama:

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

Asumo que esto sucede porque el alambre hasta los eventos después del constructor. La única alternativa que veo es crear un método Load que haga el trabajo del constructor. La desventaja es que cualquiera que use esta clase debe saber para llamar a Load antes de intentar acceder a Result (o cualquier otra propiedad).

EDIT: Aquí está la solución final:

MyObjectBuilder Clase

public class MyObjectBuilder 
    { 
     public event ProgressChangedEventHandler ProgressChanged; 

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

      return o; 
     } 
    } 

MiObjeto clase del Programa de

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();      
     } 
    } 

Clase

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

En general, he descubierto que es una mala idea que un constructor cargue datos. Te arrepentirás tarde o temprano, y si haces pruebas unitarias, será "antes". –

Respuesta

8

Otra opción sería pasar los controladores de eventos en el constructor también, por supuesto.

Personalmente trato de evitar hacer algo como esto dentro de un constructor. La creación de un nuevo objeto generalmente no debe iniciar tareas en segundo plano, IMO. Es posible que desee ponerlo en un método estático en su lugar, que puede llamar a un constructor privado, por supuesto.

También podría dividir su clase en dos: un generador que lo prepara todo (por ejemplo, los eventos) y luego una clase "en vuelo o completada", que tiene la propiedad Result. Llamarías al Start o algo similar en la primera clase para obtener una instancia de la segunda.

+0

@Jon Intenté apuñalar su idea de usar una clase de Constructor. No obstante, creo que no entiendo el concepto.Paso el controlador de eventos a MyObjectBuilder y luego devuelvo un MyObject con un método de creación. Lamentablemente, MyObject aún no está completamente cargado. ¿Alguna modificación que harías en mi código anterior? –

+0

@Blake: Bueno, no me molestaría en tener el evento como parte de MyObject; simplemente pasaría el controlador al constructor (desde MyObjectBuilder.CreateMyObject). Sin embargo, parece que debería funcionar básicamente ... ¿qué está pasando? (Tenga en cuenta que está combinando * ambos enfoques * en ese código; si está contento con que su código de muestra pase el manejador de eventos para comenzar, puede hacerlo todo en un tipo, quizás con un método estático. La idea del constructor era permitirle construirlo, agregar los controladores de eventos y * luego * llamar al método Create). –

+0

@Jon El problema principal es que en el punto de Console.WriteLine (o.Result) la salida es 0 en lugar de lo esperado 99 (después de que el ciclo se haya completado). El progreso se está actualizando correctamente, es solo que el objeto no está completamente inicializado. ¿Tener el objeto completamente inicializado es un objetivo de diseño que debería tener, o el patrón del constructor lo entiende como algo que todavía está en proceso de construcción? –

1

¿Esto es posible? Tal vez. ¿Es sabio? No.

La razón más obvia es que no se garantiza que el hilo de fondo no se ejecutará antes de que los controladores de eventos estén conectados después de la construcción del objeto. O peor, algunos de los controladores de eventos pueden estar suscritos, mientras que otros no.

Cuestiones relacionadas