Extraño comportamiento usando yield y manejo de excepciones

Trabajando en un proyecto de alguna manera llegué a ver el escenario que a continuación describo al momento de utilizar el operador yield y manejo de excepciones con PostSharp..

Resulta que cuando utilizas yield para regresar una lista tipo IEnumerable y en el método hijo en el cual utilizas yield ocurre una excepción, hay un comportamiento un tanto raro o al menos para mí que no soy un experto en C#. Quise hacer un proyecto de ejemplo para ilustrar mejor el comportamiento que describo. A continuación pongo la clase de un proyecto de consola que hice:

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PostSharpYieldSample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            ParentMethod();
            Console.WriteLine("press any key to end.");
            Console.ReadLine();
        }

        private static IEnumerable<string> _myList;

        /// <summary>
        /// Parent
        /// </summary>
        [ExceptionHandlerAspect]
        public static void ParentMethod()
        {
            Console.WriteLine("In parent Method.");
            _myList = ChildYieldMethod();

            // comment the lines below to "EAT" the exception message
            foreach (string str in _myList)
                Console.Write(str);
        }

        /// <summary>
        /// Child method using yield
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<string> ChildYieldMethod()
        {
            Console.WriteLine("In child Method.");
            String[] data = { "Item 1", "Item 2", "Item 3" };

            foreach (string myString in data)
            {
                yield return myString;
                throw new Exception("Some exception happen here");
            }
        }
    }
}

Como se puede ver estoy utilizando un Aspecto de PostSharp en el método ParentMethod() para “cachar” todas las excepciones que pudieran ocurrir en el método. A continuación enlisto el aspecto que estoy utilizando:

ExceptionHandlerAspect.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PostSharp.Aspects;
using System.Diagnostics;

namespace PostSharpYieldSample
{
    /// <summary>
    /// Aspect class that handles errors using postsharp
    /// </summary>
    [Serializable]
    public sealed class ExceptionHandlerAspect : OnExceptionAspect
    {
        /// <summary>
        /// The flow behavior to use.
        /// </summary>
        private FlowBehavior flowBehavior;

        /// <summary>
        /// Default Constructor
        /// </summary>
        public ExceptionHandlerAspect()
            : this(FlowBehavior.Return)
        {
        }

        /// <summary>
        /// Creates an instance specifying the flow behavior to use
        /// </summary>
        /// <param name="flowBehavior">The flow behavior</param>
        public ExceptionHandlerAspect(FlowBehavior flowBehavior)
        {
            this.flowBehavior = flowBehavior;
        }

        /// <summary>
        /// Method invoked upon failure of the method to which the current
        /// aspect is applied.
        /// </summary>
        /// <param name="args">Information about the method being executed.</param>
        public override void OnException(MethodExecutionArgs args)
        {
            string errorInfo = string.Format("{0}() {1} - PostSharp message.", args.Method.Name, args.Exception.Message);
            Console.WriteLine(errorInfo);
            args.FlowBehavior = flowBehavior;
        }

    }
}

Éste aspecto es muy sencillo y lo utilicé solo para fines de la demostración y lo único que hace es que me regresa un mensaje personalizado de la excepción que ocurrió en el método donde se definió el aspecto (atributo). Al ejecutar la aplicación de ejemplo se verá la siguiente pantalla:

Cosa que tiene sentido ya que el flujo del programa entró en el método donde utilizo yield y solo regresó el primer elemento para después lanzar la excepción especificada. En este caso PostSharp “atrapa” correctamente la excepción y la muestra en pantalla, este es el comportamiento esperado. El problema viene cuando comentamos las lineas del parent method donde se hace referencia a la lista que se llenó mediante yield, estas lineas comentadas son las siguientes:

    // comment the lines below to "EAT" the exception message
    //foreach (string str in _myList)
    //    Console.Write(str);

Al momento de comentar estas lineas y volver a ejecutar el programa algo “raro” ocurre con la excepción. Lo que ocurre es que prácticamente la excepción es omitida o prácticamente nuca llegamos a ejecutar el método donde utilizamos yield. el resultado luciría algo como lo siguiente:

Habrá que investigar más sobre lo que la palabra reservada yield hace en este caso internamente ya que a mi punto de vista no es un comportamiento obvio al que uno concluiría fácilmente, a continuación pongo el enlace del código fuente de la aplicación ejemplo..

PostSharpYieldSample

Pd. Creo que se me olvidó hablar un poco mas acerca de PostSharp y programación orientada a aspectos, es buen tema para un post que publicaré mas adelante en estos días por si te interesa saber más sobre el tema de todos modos en la red hay mucha información sobre ello..

¡Saludos! 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *