Vraag Is er een reden voor het hergebruik door C # van de variabele in een foreach?


Wanneer we lambda-expressies of anonieme methoden gebruiken in C #, moeten we op onze hoede zijn voor de toegang tot aangepaste sluiting valkuil. Bijvoorbeeld:

foreach (var s in strings)
{
   query = query.Where(i => i.Prop == s); // access to modified closure
   ...
}

Vanwege de gewijzigde sluiting veroorzaakt de bovenstaande code alle Where de clausules op de vraag moeten op de definitieve waarde van worden gebaseerd s.

Zoals uitgelegd hier, dit gebeurt omdat het s variabele aangegeven in foreach loop hierboven is als volgt vertaald in de compiler:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}

in plaats van als dit:

while (enumerator.MoveNext())
{
   string s;
   s = enumerator.Current;
   ...
}

Zoals gezegd hier, er zijn geen prestatievoordelen voor het declareren van een variabele buiten de lus, en onder normale omstandigheden is de enige reden die ik kan bedenken om dit te doen, als u van plan bent de variabele buiten het bereik van de lus te gebruiken:

string s;
while (enumerator.MoveNext())
{
   s = enumerator.Current;
   ...
}
var finalString = s;

Variabelen gedefinieerd in a foreach lus kan niet buiten de lus worden gebruikt:

foreach(string s in strings)
{
}
var finalString = s; // won't work: you're outside the scope.

Dus de compiler declareert de variabele op een manier die hem zeer vatbaar maakt voor een fout die vaak moeilijk te vinden en te debuggen is, terwijl hij geen waarneembare voordelen oplevert.

Is er iets dat je kunt doen? foreach loops op deze manier die je niet zou kunnen als ze werden gecompileerd met een variabele met een innerlijke scope, of is dit gewoon een willekeurige keuze die werd gemaakt voordat anonieme methoden en lambda-expressies beschikbaar of gebruikelijk waren en die sindsdien niet zijn herzien?


1482
2018-01-17 17:21


oorsprong


antwoorden:


De compiler declareert de variabele op een manier die hem zeer vatbaar maakt voor een fout die vaak moeilijk te vinden en te debuggen is, terwijl hij geen waarneembare voordelen oplevert.

Uw kritiek is volkomen gerechtvaardigd.

Ik bespreek dit probleem hier in detail:

Het sluiten van de lusvariabele als schadelijk beschouwd

Is er iets dat je kunt doen met foreach-lussen op deze manier die je niet zou kunnen als ze werden gecompileerd met een variabele met een innerlijke dimensie? of is dit slechts een willekeurige keuze die werd gemaakt voordat anonieme methoden en lambda-uitdrukkingen beschikbaar of gebruikelijk waren en die sindsdien niet zijn herzien?

Het laatste. De C # 1.0-specificatie heeft feitelijk niet gezegd of de lusvariabele zich binnen of buiten het luslichaam bevond, omdat het geen waarneembaar verschil maakte. Toen afsluitingssemantiek werd geïntroduceerd in C # 2.0, werd ervoor gekozen de lusvariabele buiten de lus te plaatsen, consistent met de "voor" -lus.

Ik denk dat het eerlijk is om te zeggen dat iedereen die beslissing betreurt. Dit is een van de ergste "gotchas" in C #, en we gaan de brekende verandering nemen om het te repareren. In C # 5 is de foreach-lusvariabele logisch binnen het lichaam van de lus, en daarom zullen sluitingen telkens een nieuw exemplaar krijgen.

De for loop zal niet worden gewijzigd en de wijziging zal niet "teruggezet" worden naar eerdere versies van C #. Daarom moet je voorzichtig blijven als je dit idioom gebruikt.


1279
2018-01-17 17:56



Wat u vraagt, wordt uitgebreid behandeld door Eric Lippert in zijn blogpost Het sluiten van de lusvariabele als schadelijk beschouwd en het vervolg.

Voor mij is het meest overtuigende argument dat het niet consistent zijn van de nieuwe variabele in elke iteratie zou zijn for(;;)stijl loop. Zou je een nieuw verwachten int i in elke iteratie van for (int i = 0; i < 10; i++)?

Het meest voorkomende probleem met dit gedrag is het maken van een afsluitingsreactievariabele en het heeft een eenvoudige oplossing:

foreach (var s in strings)
{
    var s_for_closure = s;
    query = query.Where(i => i.Prop == s_for_closure); // access to modified closure

Mijn blogpost over dit probleem: Afsluiting voor elke variabele in C #.


174
2018-01-17 17:39



Omdat ik hierdoor ben gebeten, heb ik de gewoonte om lokaal gedefinieerde variabelen op te nemen in het binnenste bereik dat ik gebruik om over te brengen naar elke afsluiting. In jouw voorbeeld:

foreach (var s in strings)
{
    query = query.Where(i => i.Prop == s); // access to modified closure

Ik doe:

foreach (var s in strings)
{
    string search = s;
    query = query.Where(i => i.Prop == search); // New definition ensures unique per iteration.

Zodra je die gewoonte hebt, kun je het vermijden in de heel zeldzaam geval dat u eigenlijk van plan was om aan de buitenste scopes te binden. Eerlijk gezegd denk ik niet dat ik dat ooit heb gedaan.


95
2018-01-17 17:47



In C # 5.0 is dit probleem opgelost en kunt u loop-variabelen sluiten en de resultaten krijgen die u verwacht.

De taalspecificatie zegt:

8.8.4 De foreach-verklaring

(...)

Een voor elke verklaring van het formulier

foreach (V v in x) embedded-statement

wordt vervolgens uitgebreid naar:

{
  E e = ((C)(x)).GetEnumerator();
  try {
      while (e.MoveNext()) {
          V v = (V)(T)e.Current;
          embedded-statement
      }
  }
  finally {
      … // Dispose e
  }
}

(...)

De plaatsing van v in de while-lus is belangrijk voor hoe het is   vastgelegd door een anonieme functie die voorkomt in de   ingebedde-statement. Bijvoorbeeld:

int[] values = { 7, 9, 13 };
Action f = null;
foreach (var value in values)
{
    if (f == null) f = () => Console.WriteLine("First value: " + value);
}
f();

Als v werd verklaard buiten de while-lus, zou het worden gedeeld   tussen alle iteraties, en de waarde ervan na de for-lus zou de   uiteindelijke waarde, 13, dat is wat de aanroeping van f zou afdrukken.   In plaats daarvan, omdat elke iteratie zijn eigen variabele heeft v, degene   vastgelegd door f in de eerste iteratie blijft de waarde behouden    7, dat is wat zal worden afgedrukt. (Opmerking: eerdere versies van C #   verklaard v buiten de while-lus.)


52
2017-09-03 13:58



Naar mijn mening is het een vreemde vraag. Het is goed om te weten hoe compiler werkt, maar dat alleen "goed om te weten".

Als u code schrijft die afhankelijk is van het algoritme van de compiler, is dit een slechte gewoonte. En het is beter om de code te herschrijven om deze afhankelijkheid uit te sluiten.

Dit is een goede vraag voor een sollicitatiegesprek. Maar in het echte leven heb ik geen problemen ondervonden die ik heb opgelost tijdens een sollicitatiegesprek.

De 90% van foreach gebruikt voor het verwerken van elk element van verzameling (niet voor selecteren of berekenen van sommige waarden). soms moet je enkele waarden binnen de lus berekenen, maar het is geen goede oefening om een ​​GROTE lus te maken.

Het is beter om LINQ-expressies te gebruiken voor het berekenen van de waarden. Omdat wanneer u veel dingen binnen de lus berekent, na 2-3 maanden wanneer u (of wie dan ook) deze code leest, de persoon niet begrijpt wat dit is en hoe het zou moeten werken.


0
2018-06-15 07:31