für vs. foreach vs. LINQ


86

Wenn ich Code in Visual Studio schreibe, empfiehlt mir ReSharper (Gott segne es!) Oft, meine alte for-Schleife in die kompaktere foreach-Form zu ändern.

Und oft, wenn ich diese Änderung akzeptiere, geht ReSharper einen Schritt weiter und schlägt mir vor, sie in einer glänzenden LINQ-Form erneut zu ändern.

Ich frage mich also: Gibt es bei diesen Verbesserungen einige echte Vorteile? Bei einer ziemlich einfachen Codeausführung kann ich (offensichtlich) keinen Geschwindigkeitsschub feststellen, aber ich kann feststellen, dass der Code immer weniger lesbar wird ... Also frage ich mich: Lohnt es sich?


2
Nur eine Anmerkung - LINQ-Syntax ist eigentlich ziemlich lesbar, wenn Sie mit SQL-Syntax vertraut sind. Es gibt auch zwei Formate für LINQ (die SQL-ähnlichen Lambda-Ausdrücke und die verketteten Methoden), die das Erlernen möglicherweise erleichtern. Es könnten nur die Vorschläge von ReSharper sein, die es unlesbar erscheinen lassen.
Shauna

3
Als Faustregel verwende ich normalerweise foreach, es sei denn, ich arbeite mit einem Array bekannter Länge oder ähnlichen Fällen, in denen die Anzahl der Iterationen relevant ist. Wenn es um LINQ geht, sehe ich normalerweise, was ReSharper aus einem foreach macht, und wenn die resultierende LINQ-Anweisung ordentlich / trivial / lesbar ist, verwende ich sie und mache sie ansonsten wieder rückgängig. Wenn es mühsam wäre, die ursprüngliche Nicht-LINQ-Logik neu zu schreiben, wenn sich die Anforderungen ändern, oder wenn es notwendig sein könnte, die Logik, von der die LINQ-Anweisung abstrahiert, detailliert zu debuggen, werde ich sie nicht LINQ und lange belassen bilden.
Ed Hastings

Ein häufiger Fehler foreachbesteht darin, Elemente aus einer Sammlung zu entfernen, während sie aufgelistet werden. In der Regel wird eine forSchleife benötigt, um vom letzten Element an zu beginnen.
Slai

Sie könnten einen Nutzen aus Øredev 2013 - Jessica Kerr - Funktionsprinzipien für objektorientierte Entwickler ziehen . Linq kommt kurz nach der 33-Minuten-Marke unter dem Titel "Declarative Style" in die Präsentation.
Theraot

Antworten:


139

for gegen foreach

Es besteht die allgemeine Verwirrung, dass diese beiden Konstrukte sehr ähnlich sind und dass beide wie folgt austauschbar sind:

foreach (var c in collection)
{
    DoSomething(c);
}

und:

for (var i = 0; i < collection.Count; i++)
{
    DoSomething(collection[i]);
}

Die Tatsache, dass beide Schlüsselwörter mit denselben drei Buchstaben beginnen, bedeutet nicht, dass sie semantisch ähnlich sind. Diese Verwirrung ist besonders für Anfänger äußerst fehleranfällig. Durch eine Sammlung zu iterieren und etwas mit den Elementen zu tun, ist erledigt mit foreach; formuss und sollte nicht für diesen Zweck verwendet werden , es sei denn, Sie wissen wirklich, was Sie tun.

Mal sehen, was daran falsch ist mit einem Beispiel. Am Ende finden Sie den vollständigen Code einer Demo-Anwendung, mit der die Ergebnisse gesammelt werden.

Im Beispiel laden wir einige Daten aus der Datenbank, genauer gesagt die Städte von Adventure Works, sortiert nach Namen, bevor wir auf "Boston" stoßen. Die folgende SQL-Abfrage wird verwendet:

select distinct [City] from [Person].[Address] order by [City]

Die Daten werden von der ListCities()Methode geladen , die ein zurückgibt IEnumerable<string>. So foreachsieht es aus:

foreach (var city in Program.ListCities())
{
    Console.Write(city + " ");

    if (city == "Boston")
    {
        break;
    }
}

Lassen Sie es uns mit a umschreiben for, vorausgesetzt, beide sind austauschbar:

var cities = Program.ListCities();
for (var i = 0; i < cities.Count(); i++)
{
    var city = cities.ElementAt(i);

    Console.Write(city + " ");

    if (city == "Boston")
    {
        break;
    }
}

Beide kehren in die gleichen Städte zurück, aber es gibt einen großen Unterschied.

  • Bei der Verwendung foreach, ListCities()wird einmal aufgerufen und liefert 47 Artikel.
  • Bei der Verwendung for, ListCities()94 mal aufgerufen und ergibt insgesamt 28.153 Artikel.

Was ist passiert?

IEnumerableist faul . Dies bedeutet, dass die Arbeit nur in dem Moment erledigt wird, in dem das Ergebnis benötigt wird. Lazy Evaluation ist ein sehr nützliches Konzept, hat jedoch einige Einschränkungen, einschließlich der Tatsache, dass es leicht ist, die Momente zu übersehen, in denen das Ergebnis benötigt wird, insbesondere in den Fällen, in denen das Ergebnis mehrmals verwendet wird.

In einem Fall von a foreachwird das Ergebnis nur einmal angefordert. In einem Fall von a, for wie in dem oben falsch geschriebenen Code implementiert , wird das Ergebnis 94-mal angefordert , dh 47 × 2:

  • Jedes Mal cities.Count()wird aufgerufen (47 Mal),

  • Jedes Mal cities.ElementAt(i)wird aufgerufen (47 Mal).

94-maliges Abfragen einer Datenbank ist schrecklich, aber nicht das Schlimmste, was passieren kann. Stellen Sie sich zum Beispiel vor, was passieren würde, wenn der selectAbfrage eine Abfrage vorangehen würde, die auch eine Zeile in die Tabelle einfügt. Richtig, wir hätten fordie Datenbank 2.147.483.647 Mal aufgerufen , es sei denn, sie stürzt hoffentlich vorher ab.

Natürlich ist mein Code voreingenommen. Ich habe absichtlich die Faulheit von genutzt IEnumerableund es so geschrieben, dass es immer wieder anruft ListCities(). Man kann feststellen, dass ein Anfänger dies niemals tun wird, weil:

  • Der IEnumerable<T>hat nicht die Eigenschaft Count, sondern nur die Methode Count(). Das Aufrufen einer Methode ist beängstigend und es ist zu erwarten, dass das Ergebnis nicht zwischengespeichert und in einem for (; ...; )Block nicht geeignet ist .

  • Die Indizierung ist für IEnumerable<T>nicht verfügbar und es ist nicht offensichtlich, die ElementAtLINQ-Erweiterungsmethode zu finden .

Wahrscheinlich würden die meisten Anfänger das Ergebnis einfach ListCities()in etwas umwandeln, mit dem sie vertraut sind, wie z List<T>.

var cities = Program.ListCities();
var flushedCities = cities.ToList();
for (var i = 0; i < flushedCities.Count; i++)
{
    var city = flushedCities[i];

    Console.Write(city + " ");

    if (city == "Boston")
    {
        break;
    }
}

Dieser Code unterscheidet sich jedoch stark von der foreachAlternative. Auch hier gibt es die gleichen Ergebnisse, und dieses Mal wird die ListCities()Methode nur einmal aufgerufen, ergibt jedoch 575 Elemente, während sie mit foreachnur 47 Elemente ergibt .

Der Unterschied besteht darin, ToList()dass alle Daten aus der Datenbank geladen werden. Während foreachnur die Städte vor "Boston" angefordert werden, müssen für die neue Stadt foralle Städte abgerufen und gespeichert werden. Mit 575 kurzen Zeichenfolgen macht es wahrscheinlich keinen großen Unterschied, aber was wäre, wenn wir nur wenige Zeilen aus einer Tabelle mit Milliarden von Datensätzen abrufen würden?

Also, was ist das foreacheigentlich?

foreachist näher an einer while-Schleife. Der Code, den ich zuvor verwendet habe:

foreach (var city in Program.ListCities())
{
    Console.Write(city + " ");

    if (city == "Boston")
    {
        break;
    }
}

kann einfach ersetzt werden durch:

using (var enumerator = Program.ListCities().GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        var city = enumerator.Current;
        Console.Write(city + " ");

        if (city == "Boston")
        {
            break;
        }
    }
}

Beide produzieren die gleiche IL. Beide haben das gleiche Ergebnis. Beide haben die gleichen Nebenwirkungen. Natürlich whilekann dies in einer ähnlichen Unendlichkeit umgeschrieben werden for, aber es wäre noch länger und fehleranfällig. Es steht Ihnen frei, diejenige zu wählen, die Sie besser lesen können.

Willst du es selbst testen? Hier ist der vollständige Code:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;

public class Program
{
    private static int countCalls;

    private static int countYieldReturns;

    public static void Main()
    {
        Program.DisplayStatistics("for", Program.UseFor);
        Program.DisplayStatistics("for with list", Program.UseForWithList);
        Program.DisplayStatistics("while", Program.UseWhile);
        Program.DisplayStatistics("foreach", Program.UseForEach);

        Console.WriteLine("Press any key to continue...");
        Console.ReadKey(true);
    }

    private static void DisplayStatistics(string name, Action action)
    {
        Console.WriteLine("--- " + name + " ---");

        Program.countCalls = 0;
        Program.countYieldReturns = 0;

        var measureTime = Stopwatch.StartNew();
        action();
        measureTime.Stop();

        Console.WriteLine();
        Console.WriteLine();
        Console.WriteLine("The data was called {0} time(s) and yielded {1} item(s) in {2} ms.", Program.countCalls, Program.countYieldReturns, measureTime.ElapsedMilliseconds);
        Console.WriteLine();
    }

    private static void UseFor()
    {
        var cities = Program.ListCities();
        for (var i = 0; i < cities.Count(); i++)
        {
            var city = cities.ElementAt(i);

            Console.Write(city + " ");

            if (city == "Boston")
            {
                break;
            }
        }
    }

    private static void UseForWithList()
    {
        var cities = Program.ListCities();
        var flushedCities = cities.ToList();
        for (var i = 0; i < flushedCities.Count; i++)
        {
            var city = flushedCities[i];

            Console.Write(city + " ");

            if (city == "Boston")
            {
                break;
            }
        }
    }

    private static void UseForEach()
    {
        foreach (var city in Program.ListCities())
        {
            Console.Write(city + " ");

            if (city == "Boston")
            {
                break;
            }
        }
    }

    private static void UseWhile()
    {
        using (var enumerator = Program.ListCities().GetEnumerator())
        {
            while (enumerator.MoveNext())
            {
                var city = enumerator.Current;
                Console.Write(city + " ");

                if (city == "Boston")
                {
                    break;
                }
            }
        }
    }

    private static IEnumerable<string> ListCities()
    {
        Program.countCalls++;
        using (var connection = new SqlConnection("Data Source=mframe;Initial Catalog=AdventureWorks;Integrated Security=True"))
        {
            connection.Open();

            using (var command = new SqlCommand("select distinct [City] from [Person].[Address] order by [City]", connection))
            {
                using (var reader = command.ExecuteReader(CommandBehavior.SingleResult))
                {
                    while (reader.Read())
                    {
                        Program.countYieldReturns++;
                        yield return reader["City"].ToString();
                    }
                }
            }
        }
    }
}

Und die Ergebnisse:

--- für ---
Abingdon Albany Alexandria Alhambra [...] Bonn Bordeaux Boston

Die Daten wurden 94 Mal aufgerufen und ergaben 28153 Artikel.

--- für mit Liste ---
Abingdon Albany Alexandria Alhambra [...] Bonn Bordeaux Boston

Die Daten wurden 1 Mal aufgerufen und ergaben 575 Artikel.

--- während ---
Abingdon Albany Alexandria Alhambra [...] Bonn Bordeaux Boston

Die Daten wurden 1 Mal aufgerufen und ergaben 47 Artikel.

--- foreach ---
Abingdon Albany Alexandria Alhambra [...] Bonn Bordeaux Boston

Die Daten wurden 1 Mal aufgerufen und ergaben 47 Artikel.

LINQ vs. traditioneller Weg

Was LINQ betrifft, möchten Sie vielleicht Functional Programming (FP) lernen - nicht C # FP-Zeug, sondern echte FP-Sprache wie Haskell. In funktionalen Sprachen kann der Code auf bestimmte Weise ausgedrückt und dargestellt werden. In einigen Situationen ist es nicht-funktionalen Paradigmen überlegen.

FP ist dafür bekannt, dass es viel besser ist, Listen zu manipulieren ( Liste als Oberbegriff, unabhängig von List<T>). Angesichts dieser Tatsache ist die Möglichkeit, C # -Code in Bezug auf Listen funktionaler auszudrücken, eher eine gute Sache.

Wenn Sie nicht überzeugt sind, vergleichen Sie die Lesbarkeit von Code, der sowohl funktional als auch nicht funktional in meiner vorherigen Antwort zu diesem Thema geschrieben wurde.


1
Frage zum ListCities () Beispiel. Warum sollte es nur einmal laufen? Ich hatte in der Vergangenheit keine Probleme damit, Renditen zu erzielen.
Dante

1
Er sagt nicht, dass Sie nur ein Ergebnis von IEnumerable erhalten würden - er sagt, dass die SQL-Abfrage (die den teuren Teil der Methode darstellt) nur einmal ausgeführt würde - das ist eine gute Sache. Anschließend werden alle Ergebnisse der Abfrage gelesen und ausgegeben.
HappyCat

9
@Giorgio: Auch wenn diese Frage verständlich ist, würde es uns nicht zu einer sehr effektiven Sprache führen, wenn die Semantik einer Sprache darüber nachdenkt, was Anfänger verwirrend finden könnten.
Steven Evers

4
LINQ ist nicht nur semantischer Zucker. Es bietet eine verzögerte Ausführung. Und im Fall von IQueryables (z. B. Entity Framework) kann die Abfrage übergeben und zusammengestellt werden, bis sie iteriert wird (was bedeutet, dass das Hinzufügen einer where-Klausel zu einer zurückgegebenen IQueryable dazu führt, dass die SQL bei der Iteration an den Server übergeben wird, um diese where-Klausel einzuschließen Auslagerung der Filterung auf den Server).
Michael Brown

8
So sehr mir diese Antwort gefällt, denke ich, dass die Beispiele ein wenig erfunden sind. Die Zusammenfassung am Ende legt nahe, dass dies foreacheffizienter ist, als forwenn die Disparität tatsächlich auf absichtlich gebrochenen Code zurückzuführen ist. Die Gründlichkeit der Antwort macht sich bemerkbar, aber es ist leicht einzusehen, wie ein gelegentlicher Beobachter zu den falschen Schlussfolgerungen kommen kann.
Robert Harvey

19

Zwar gibt es bereits einige großartige Ausführungen zu den Unterschieden zwischen for und foreach. Es gibt einige grobe Falschdarstellungen der Rolle von LINQ.

LINQ-Syntax ist nicht nur syntaktischer Zucker, der eine funktionale Programmiernäherung an C # bietet. LINQ bietet funktionale Konstrukte mit allen Vorteilen für C #. In Kombination mit der Rückgabe von IEnumerable anstelle von IList bietet LINQ eine verzögerte Ausführung der Iteration. In der Regel konstruieren die Benutzer jetzt eine IList und geben sie von ihren Funktionen wie dieser zurück

public IList<Foo> GetListOfFoo()
{
   var retVal=new List<Foo>();
   foreach(var foo in _myPrivateFooList)
   {
      if(foo.DistinguishingValue == check)
      {
         retVal.Add(foo);
      }
   }
   return retVal;
}

Verwenden Sie stattdessen die Yield Return-Syntax , um eine verzögerte Aufzählung zu erstellen.

public IEnumerable<Foo> GetEnumerationOfFoo()
{
   //no need to create an extra list
   //var retVal=new List<Foo>();
   foreach(var foo in _myPrivateFooList)
   {
      if(foo.DistinguishingValue == check)
      {
         //yield the match compiler handles the complexity
         yield return foo;
      }
   }
   //no need for returning a list
   //return retVal;
}

Jetzt wird die Aufzählung erst durchgeführt, wenn Sie sie in eine ToList aufnehmen oder darüber iterieren. Und es tritt nur bei Bedarf auf (hier ist eine Aufzählung von Fibbonaci, die kein Stapelüberlaufproblem hat)

/**
Returns an IEnumerable of fibonacci sequence
**/
public IEnumerable<int> Fibonacci()
{
  int first, second = 1;
  yield return first;
  yield return second;
  //the 46th fibonacci number is the largest that
  //can be represented in 32 bits. 
  for (int i = 3; i < 47; i++)
  {
    int retVal = first+second;
    first=second;
    second=retVal;
    yield return retVal;
  }
}

Wenn Sie ein Foreach über die Fibonacci-Funktion durchführen, wird die Sequenz 46 zurückgegeben. Wenn Sie die 30. wollen, ist das alles, was berechnet wird

var thirtiethFib=Fibonacci().Skip(29).Take(1);

Was uns viel Spaß macht, ist die Unterstützung in der Sprache für Lambda-Ausdrücke (in Kombination mit den Konstrukten IQueryable und IQueryProvider ermöglicht dies die funktionale Komposition von Abfragen für eine Vielzahl von Datensätzen; der IQueryProvider ist für die Interpretation der übergebenen Daten verantwortlich Ausdrücke und Erstellen und Ausführen einer Abfrage unter Verwendung der nativen Konstrukte der Quelle). Ich werde hier nicht auf die Details eingehen, aber es gibt eine Reihe von Blog-Posts, die zeigen, wie man hier einen SQL-Abfrageanbieter erstellt

Zusammenfassend sollten Sie IEnumerable vor IList zurückgeben, wenn die Konsumenten Ihrer Funktion eine einfache Iteration durchführen. Nutzen Sie die Funktionen von LINQ, um die Ausführung komplexer Abfragen zu verschieben, bis sie benötigt werden.


13

aber ich kann sehen, dass der Code immer weniger lesbar wird

Die Lesbarkeit liegt im Auge des Betrachters. Einige Leute könnten sagen

var common = list1.Intersect(list2);

ist perfekt lesbar; andere könnten sagen, dass dies undurchsichtig ist und es vorziehen würde

List<int> common = new List<int>();
for(int i1 = 0; i1 < list1.Count; i1++)
{
    for(int i2 = 0; i2 < list2.Count; i2++)
    {
        if (list1[i1] == list2[i2])
        {
            common.Add(i1);
            break;
        }
    }
}

als es klarer zu machen, was getan wird. Wir können Ihnen nicht sagen, was Sie besser lesen können. In dem Beispiel, das ich hier erstellt habe, können Sie jedoch möglicherweise einige meiner Vorurteile erkennen ...


28
Ehrlich gesagt würde ich sagen, dass Linq die Absicht objektiv lesbarer macht, während for-Schleifen den Mechanismus objektiv lesbarer machen.
jk.

16
Ich würde so schnell ich kann von jemandem rennen, der mir sagt, dass die For-for-If-Version besser lesbar ist als die Intersect-Version.
Konamiman

3
@Konamiman - Das hängt davon ab, wonach eine Person sucht, wenn sie an "Lesbarkeit" denkt. Der Kommentar von jk. verdeutlicht dies perfekt. Die Schleife ist in dem Sinne lesbarer, dass Sie leicht sehen können, wie das Endergebnis erzielt wird, während der LINQ in Bezug auf das Endergebnis besser lesbar ist.
Shauna

2
Deshalb geht die Schleife in die Implementierung und Sie verwenden Intersect überall.
R. Martinho Fernandes

8
@Shauna: Stellen Sie sich die for-loop-Version in einer Methode vor, die verschiedene andere Dinge ausführt. Es ist ein Chaos. Natürlich teilen Sie es in eine eigene Methode auf. In Bezug auf die Lesbarkeit ist dies dasselbe wie IEnumerable <T> .Intersect, aber jetzt haben Sie die Framework-Funktionalität dupliziert und mehr Code zum Verwalten eingeführt. Die einzige Entschuldigung ist, wenn Sie aus Verhaltensgründen eine benutzerdefinierte Implementierung benötigen, wir hier jedoch nur über die Lesbarkeit sprechen.
Misko

7

Der Unterschied zwischen LINQ und besteht foreachin zwei verschiedenen Programmierstilen: imperativ und deklarativ.

  • Imperativ: In diesem Stil sagen Sie dem Computer: "Tu das ... jetzt tu das ... jetzt tu das, jetzt tu das". Sie füttern es ein Programm Schritt für Schritt.

  • Deklarativ: In diesem Stil teilen Sie dem Computer mit, wie das Ergebnis aussehen soll, und lassen ihn herausfinden, wie er dahin kommt.

Ein klassisches Beispiel für diese beiden Stile ist das Vergleichen von Assemblycode (oder C) mit SQL. In der Montage erteilen Sie Anweisungen (wörtlich) nacheinander. In SQL legen Sie fest, wie Daten zusammengefügt werden und welches Ergebnis Sie aus diesen Daten erzielen möchten.

Ein netter Nebeneffekt der deklarativen Programmierung ist, dass sie tendenziell etwas höher ist. Auf diese Weise kann sich die Plattform unter Ihnen entwickeln, ohne dass Sie Ihren Code ändern müssen. Zum Beispiel:

var foo = bar.Distinct();

Was passiert hier? Verwendet Distinct einen Kern? Zwei? Fünfzig? Wir wissen es nicht und es interessiert uns nicht. Die .NET-Entwickler könnten es jederzeit umschreiben, solange es denselben Zweck erfüllt, zu dem unser Code nach einem Code-Update auf magische Weise schneller werden könnte.

Das ist die Kraft der funktionalen Programmierung. Und der Grund, warum Sie diesen Code in Sprachen wie Clojure, F # und C # (geschrieben mit einer funktionalen Programmier-Denkweise) finden, ist oft 3x-10x kleiner als die zwingenden Gegenstücke.

Schließlich mag ich den deklarativen Stil, weil ich in C # die meiste Zeit Code schreiben kann, der die Daten nicht verändert. Im obigen Beispiel Distinct()ändert sich der Balken nicht, es wird eine neue Kopie der Daten zurückgegeben. Dies bedeutet, dass sich die Bar, von der auch immer sie stammt, nicht plötzlich ändert.

Lernen Sie also, wie die anderen Plakate sagen, die funktionale Programmierung. Es wird dein Leben verändern. Und wenn Sie können, tun Sie dies in einer echten funktionalen Programmiersprache. Ich bevorzuge Clojure, aber auch F # und Haskell sind eine ausgezeichnete Wahl.


2
Die LINQ-Verarbeitung wird zurückgestellt, bis Sie sie tatsächlich durchlaufen haben. var foo = bar.Distinct()ist im Wesentlichen ein IEnumerator<T>bis Sie anrufen .ToList()oder .ToArray(). Dies ist ein wichtiger Unterschied, denn wenn Sie sich dessen nicht bewusst sind, kann dies zu schwer verständlichen Fehlern führen.
Berin Loritsch

-5

Können andere Entwickler im Team LINQ lesen?

Wenn nicht, dann benutze es nicht oder eins von zwei Dingen wird passieren:

  1. Ihr Code wird nicht mehr zu pflegen sein
  2. Sie müssen nur noch Ihren gesamten Code und alles, was darauf beruht, verwalten

A für jede Schleife ist perfekt, um eine Liste zu durchlaufen, aber wenn Sie das nicht tun, verwenden Sie keine.


11
hmm, ich weiß zu schätzen, dass dies für ein einzelnes Projekt die Antwort sein kann, aber mittel- bis langfristig sollten Sie Ihre Mitarbeiter schulen, sonst haben Sie ein Problem mit dem Codeverständnis, das sich nicht nach einer guten Idee anhört.
jk.

21
Tatsächlich könnte eine dritte Sache passieren: Die anderen Entwickler könnten sich ein wenig anstrengen und tatsächlich etwas Neues und Nützliches lernen. Das ist nicht ungewöhnlich.
Eric King

6
@InvertedLlama Wenn ich in einem Unternehmen wäre, in dem die Entwickler eine Schulung benötigen, um neue Sprachkonzepte zu verstehen, würde ich darüber nachdenken, ein neues Unternehmen zu finden.
Wyatt Barnett

13
Vielleicht können Sie mit Bibliotheken mit dieser Einstellung durchkommen, aber wenn es um die wichtigsten Sprachfunktionen geht, ist das nicht schlimm. Sie können Frameworks auswählen. Ein guter .NET-Programmierer muss jedoch alle Funktionen der Sprache und der Kernplattform (System. *) Verstehen. Und wenn man bedenkt, dass man EF nicht einmal richtig verwenden kann, ohne Linq zu verwenden, muss ich sagen ... Wenn man heutzutage ein .NET-Programmierer ist und Linq nicht kennt, ist man inkompetent.
Timothy Baldridge

7
Das hat schon genug Abwertungen, also werde ich dem nicht hinzufügen, aber ein Argument, das ignorante / inkompetente Mitarbeiter unterstützt, ist niemals ein gültiges.
Steven Evers
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.