Vraag een zorg over het rendement en het breken van een foreach


Is er een goede manier om van een foreach te breken, zodat de IEnumerable <> weet dat ik klaar ben en het op moet ruimen.

Beschouw de volgende code:

    private static IEnumerable<Person> getPeople()
    {
        using (SqlConnection sqlConnection = new SqlConnection("..."))
        {
            try
            {
                sqlConnection.Open();
                using (SqlCommand sqlCommand = new SqlCommand("select id, firstName, lastName from people", sqlConnection))
                {

                    using (SqlDataReader reader = sqlCommand.ExecuteReader())
                    {
                        while (reader.Read())
                            yield return new Person(reader.GetGuid(0), reader.GetString(1), reader.GetString(2));
                    }
                }
            }
            finally
            {
                Console.WriteLine("finally disposing of the connection");
                if (sqlConnection.State == System.Data.ConnectionState.Open)
                    sqlConnection.Close();
            }
        }
    }

Als de consument niet van de foreach afbreekt, is alles in orde en retourneert de lezer false, de while-lus willend en de functie ruimt de databaseopdracht en verbinding op. Maar wat gebeurt er als de beller van de foreach afbreekt voordat ik klaar ben?


21
2017-09-10 15:08


oorsprong


antwoorden:


Uitstekende vraag. U hoeft zich hier geen zorgen over te maken; de compiler zorgt voor je. Kort gezegd, wat we doen is dat we de opschooncode voor de uiteindelijke blokken in een speciale opschoningsmethode op de gegenereerde iterator plaatsen. Wanneer de controle het Foreach-blok van de beller verlaat, genereert de compiler code die de opschooncode op de iterator oproept.

Een vereenvoudigd voorbeeld:

static IEnumerable<int> GetInts()
{
    try { yield return 1; yield return 2;} 
    finally { Cleanup(); }
}

Uw vraag is in feite "Is opruimen () genoemd in dit scenario?"

foreach(int i in GetInts()) { break; }

Ja. Het iteratorblok wordt gegenereerd als een klasse met een Dispose-methode die Opschonen oproept en vervolgens wordt de foreach-lus gegenereerd als iets dat lijkt op:

{
  IEnumerator<int> enumtor = GetInts().GetEnumerator();
  try
  {
    while(enumtor.MoveNext())
    {
      i = enumtor.Current;
      break;
    }
  }
  finally
  {
    enumtor.Dispose();
  }
}

Dus wanneer de breuk gebeurt, neemt de speler eindelijk het roer over en wordt de ontdoener gebeld.

Zie mijn recente serie artikelen als u meer informatie wilt over enkele van de vreemde hoekcases die we in het ontwerp van deze functie hebben overwogen.

http://blogs.msdn.com/ericlippert/archive/tags/Iterators/default.aspx


32
2017-09-10 16:23



Eens kijken of ik je vraag krijg.

foreach(Person p in getPeople())
{
    // break here
}

vanwege het foreach-trefwoord is de Enumerator op de juiste manier verwijderd. Tijdens de verwijdering van Enumerator wordt de uitvoering van getPeople () beƫindigd. Dus de verbinding is goed opgeruimd.


2
2017-09-10 15:17



U kunt de verklaring gebruiken

yield break;

om vroegtijdig uit een rendementsloop te komen, maar je code onthult een misverstand dat ik denk ... Wanneer u de instructie "using" gebruikt,

using (SqlConnection sqlConnection = new SqlConnection("...")) 
{
   //  other stuff
}

je krijgt AUTOMATISCH een try-to-end in de gecompileerde IL-code, en het finale blok roept op om te verwijderen, en in de Dispose-code wordt de verbinding gesloten ...


2
2017-09-10 15:10