web-dev-qa-db-fra.com

rapport rendement/rendement IEnumerable <T>

J'ai remarqué quelque chose de curieux à propos de la lecture d'une IDataReader dans une déclaration using que je ne peux pas comprendre. Bien que je suis sûr que la réponse est simple.

Pourquoi se trouve-t-il dans le using (SqlDataReader rd) { ... } si je réalise directement un yield return, le lecteur reste ouvert pendant toute la durée de la lecture? Mais si j'effectue une return directe en appelant une méthode d'extension SqlDataReader (décrite ci-dessous) que le lecteur ferme avant que l'énumérable ne puisse être actualisée?

public static IEnumerable<T> Enumerate<T>(this SqlDataReader rd)
{
    while (rd.Read())
        yield return rd.ConvertTo<T>(); //extension method wrapping FastMember

    rd.NextResult();
}

Pour être tout à fait clair sur ce que je demande, je ne sais pas pourquoi les éléments suivants sont fondamentalement différents:

Un exemple complet, selon la demande de @ TimSchmelter:

/*
 * contrived methods
 */
public IEnumerable<T> ReadSomeProc<T>() {
    using (var db = new SqlConnection("connection string"))
    {
        var cmd = new SqlCommand("dbo.someProc", db);

        using(var rd = cmd.ExecuteReader())
        {
            while(rd.Read())
                yield return rd.ConvertTo<T>(); //extension method wrapping FastMember
        }
    }
}


//vs
public IEnumerable<T> ReadSomeProcExt<T>() {
    using (var db = new SqlConnection("connection string"))
    {
        var cmd = new SqlCommand("dbo.someProc", db);

        using(var rd = cmd.ExecuteReader())
        {
            return rd.Enumerate<T>(); //outlined above
        }
    }
}

/*
 * usage
 */
var lst = ReadSomeProc<SomeObect>();

foreach(var l in lst){
    //this works
}

//vs
var lst2 = ReadSomeProcExt<SomeObect>();

foreach(var l in list){
    //throws exception, invalid attempt to read when reader is closed
}
13
pimbrouwers

Résumé: Les deux versions de la méthode defer , mais ReadSomeProcExt ne différant pas l'exécution, le lecteur est supprimé avant que l'exécution ne soit renvoyée à l'appelant (c'est-à-dire avant que Enumerate<T> puisse être exécuté). ReadSomeProc, en revanche, ne crée pas le lecteur tant qu'il n'a pas été renvoyé à l'appelant. Le conteneur ne dispose donc pas du conteneur tant que toutes ses valeurs n'ont pas été lues.

Lorsque votre méthode utilise yield return, le compilateur modifie en réalité le code compilé pour renvoyer un IEnumerable<> et le code de votre méthode ne sera pas exécuté tant que tout autre code n'aura pas commencé à itérer sur le IEnumerable<>renvoyé.

Cela signifie que le code ci-dessous n'exécute même pas la première ligne de votre méthode Enumerate avant de disposer le lecteur et de renvoyer une valeur. Au moment où une autre personne commence à parcourir le IEnumerable<> renvoyé, le lecteur a déjà été supprimé. 

using(SqlDataReader rd = cmd.ExecuteReader()){
    return rd.Enumerate<T>();
}

Mais ce code exécuterait la méthode complète Enumerate() afin de produire un List<> de résultats avant de renvoyer:

using(SqlDataReader rd = cmd.ExecuteReader()){
    return rd.Enumerate<T>().ToList();
}

D'autre part, celui qui appelle la méthode avec ce code n'exécute pas réellement la méthode jusqu'à ce que le résultat soit évalué:

using(SqlDataReader rd = cmd.ExecuteReader()){
    while(rd.Read())
        yield return rd.ConvertTo<T>(); //extension method wrapping FastMember
}

Mais au moment où ils exécutent le IEnumerable<> renvoyé, le bloc using s'ouvre et il ne quitte pas Dispose() tant que le IEnumerable<> n'a pas terminé ses itérations, moment auquel vous aurez déjà lu tout ce dont vous avez besoin depuis le lecteur de données.

9
StriplingWarrior

C'est parce que le "return return" retournera un élément et poursuivra l'itération, tandis que le "normal" retournera l'invocation.

0
Wilmer