none
calling a delegate on exception catch? RRS feed

  • Question

  • I'm implementing a method [or class] that processes multiple files, and if it has trouble with one file, I don't want the problem to block all the others. But I don't want my method to display its own form.

     

    Therefore, I plan to pass an delegate to the method, which will be called whenever an exception is handled. That way, the method can resume processing the next file after the problem with the current one has been reported and logged.

     

    (Likewise, I plan to have my method call another delegate when it finishes with each file, so the progress bar can be updated.)

     

    I want to keep UI dependencies out of this method's class, to facilitate unit testing and maintainability.

     

    Do you see any hazards in this approach? Any recommendations for implementation?

    Thanks.

     

    Here's where I'm heading with the ExceptionEventArgs:

    Code Block

            /// <summary>

            /// This is for handling an exception in the middle of processing that

            /// must resume, but without dependencies for UI display and logging.

            /// </summary>

            public class ExceptionEventArgs : EventArgs

            {

                /// <summary>

                /// This supports delegating handling of any exception type, e.g.

                /// OnException(new ExceptionEventArgs(e, e.GetType(), filename));

                /// </summary>

                /// <param name="e"></param>

                /// <param name="type"></param>

                /// <param name="contextInfo"></param>

                public ExceptionEventArgs(Exception e, Type type, string contextInfo)

                {

                    exception = e;

                    this.type = type;

                    this.contextInfo = contextInfo;

                }

                private Exception exception;

                private Type type;

                private string contextInfo;

     

                /// <summary>

                /// Gets or sets context information.

                /// </summary>

                public string ContextInfo

                {

                    get { return contextInfo; }

                    set { contextInfo = value; }

                }

            

                /// <summary>

                /// Gets Exception.

                /// </summary>

                public Exception Exception

                {

                    get { return exception; }

                }

     

                /// <summary>

                /// Gets original type of exception, to facilitate handling

                /// different exception types with different logic.

                /// </summary>

                public Type Type

                {

                    get { return type; }

                }

            }

     

     

    Thursday, September 27, 2007 7:58 PM

Answers

  • The implementation passing delegates turns out to be fairly simple. Here is code from the form:

    Code Block

        private Operation operation = new Operation();

        private bool CancelRequested = false;

     

        public Form2()

        {

            InitializeComponent();

        }

     

        private void startButton_Click(object sender, EventArgs e)

        {

            CancelRequested = false;

            SetButtonEnabledState("running");

            string state = operation.ProcessFiles(OnFileCount, OnProgress,

                OnException, ref CancelRequested);

            SetButtonEnabledState(state);

        }

     

        private void SetButtonEnabledState(string state)

        {

            statusLabel.Text = state;

            startAsyncButton.Enabled = (state != "running");

            cancelAsyncButton.Enabled = (state == "running");

            progressBar.Visible = (state == "running");

        }

     

        private void OnFileCount(int fileCount)

        {

            progressBar.Maximum = fileCount;

        }

     

        private void OnException(Exception e, Type originalExceptionType)

        {

            if (MessageBox.Show(e.Message, "Error", MessageBoxButtons.OKCancel)

                == DialogResult.Cancel)

            {

                CancelRequested = true;

            }

        }

     

        private void OnProgress(int portionComplete)

        {

            progressBar.Value = portionComplete;

            statusLabel.Text = string.Format("Processing file {0} of {1}",

                portionComplete, progressBar.Maximum);

            Application.DoEvents();

        }

     

        private void cancelButton_Click(object sender, EventArgs e)

        {

            CancelRequested = true;

        }

     

     

     Here is code that simulates the processing:

    Code Block

        public delegate void FileCountDelegate(int fileCount);

        public delegate void ProgressDelegate(int portionComplete);

        public delegate void ExceptionDelegate(Exception e, Type exceptionType);

     

        public string ProcessFiles(FileCountDelegate reportFileCount,

            ProgressDelegate reportProgress, ExceptionDelegate reportException,

            ref bool cancelRequested)

        {

            const int fileCount = 10;

            reportFileCount(fileCount);

            for (int i = 1; i <= fileCount; i++)

            {

                reportProgress(i);

                if (cancelRequested)

                {

                    return "canceled";

                }

     

                Thread.Sleep(500);

     

                if (i == 7)

                {

                    Exception e = new Exception("simulated exception");

                    reportException(e, e.GetType());

                }

            }

            return "done";

        }

     

     

    I tried using the BackgroundWorker object, but found its behavior unwieldy for repeat exceptions, as the background thread continues to run (perhaps there is a way to resolve this), and it requires more coding.

    Thursday, October 11, 2007 7:59 PM

All replies

  • The general idea sounds acceptable, but why are you thinking of passing the delegate into the method, surely you should jsut define an event on the class and if the the exception occurs call the event, thus you are de-coupled from the caller and the caller uses the event handler on the class if they are interested in being notified of the event.

     

    HTH

     

    Ollie Riches

    Friday, September 28, 2007 9:02 AM
  • Good point -- I like the idea of decoupling, but in this case, I think it's simpler to pass a delegate, to make it clearer in the code that a delegate is expected/needed, and to reduce the number of separate objects that need to be coded and maintained.
    Friday, September 28, 2007 1:17 PM
  • I agree with Ollie. I would define an event that would be fired on the exception and anyone would be able to subscribe to this events, any paramaters would be passed through event args.

     

    Saturday, September 29, 2007 2:11 AM
  • The implementation passing delegates turns out to be fairly simple. Here is code from the form:

    Code Block

        private Operation operation = new Operation();

        private bool CancelRequested = false;

     

        public Form2()

        {

            InitializeComponent();

        }

     

        private void startButton_Click(object sender, EventArgs e)

        {

            CancelRequested = false;

            SetButtonEnabledState("running");

            string state = operation.ProcessFiles(OnFileCount, OnProgress,

                OnException, ref CancelRequested);

            SetButtonEnabledState(state);

        }

     

        private void SetButtonEnabledState(string state)

        {

            statusLabel.Text = state;

            startAsyncButton.Enabled = (state != "running");

            cancelAsyncButton.Enabled = (state == "running");

            progressBar.Visible = (state == "running");

        }

     

        private void OnFileCount(int fileCount)

        {

            progressBar.Maximum = fileCount;

        }

     

        private void OnException(Exception e, Type originalExceptionType)

        {

            if (MessageBox.Show(e.Message, "Error", MessageBoxButtons.OKCancel)

                == DialogResult.Cancel)

            {

                CancelRequested = true;

            }

        }

     

        private void OnProgress(int portionComplete)

        {

            progressBar.Value = portionComplete;

            statusLabel.Text = string.Format("Processing file {0} of {1}",

                portionComplete, progressBar.Maximum);

            Application.DoEvents();

        }

     

        private void cancelButton_Click(object sender, EventArgs e)

        {

            CancelRequested = true;

        }

     

     

     Here is code that simulates the processing:

    Code Block

        public delegate void FileCountDelegate(int fileCount);

        public delegate void ProgressDelegate(int portionComplete);

        public delegate void ExceptionDelegate(Exception e, Type exceptionType);

     

        public string ProcessFiles(FileCountDelegate reportFileCount,

            ProgressDelegate reportProgress, ExceptionDelegate reportException,

            ref bool cancelRequested)

        {

            const int fileCount = 10;

            reportFileCount(fileCount);

            for (int i = 1; i <= fileCount; i++)

            {

                reportProgress(i);

                if (cancelRequested)

                {

                    return "canceled";

                }

     

                Thread.Sleep(500);

     

                if (i == 7)

                {

                    Exception e = new Exception("simulated exception");

                    reportException(e, e.GetType());

                }

            }

            return "done";

        }

     

     

    I tried using the BackgroundWorker object, but found its behavior unwieldy for repeat exceptions, as the background thread continues to run (perhaps there is a way to resolve this), and it requires more coding.

    Thursday, October 11, 2007 7:59 PM