C# Language
LINQ-Abfragen
Suche…
Einführung
LINQ ist eine Abkürzung für L anguage IN tegrated Q uery. Es ist ein Konzept, das eine Abfragesprache integriert, indem ein konsistentes Modell für das Arbeiten mit Daten über verschiedene Arten von Datenquellen und -formaten hinweg bereitgestellt wird. Sie verwenden dieselben grundlegenden Codierungsmuster zum Abfragen und Umwandeln von Daten in XML-Dokumenten, SQL-Datenbanken, ADO.NET-Datensätzen, .NET-Sammlungen und allen anderen Formaten, für die ein LINQ-Anbieter verfügbar ist.
Syntax
Abfragesyntax:
- von <Bereichsvariable> in <Sammlung>
- [von <Bereichsvariable> in <Sammlung>, ...]
- <Filter, Verknüpfen, Gruppieren, Aggregatoperatoren, ...> <Lambda-Ausdruck>
- <select oder groupBy operator> <formulieren Sie das Ergebnis>
Methodensyntax:
- Enumerable.Aggregate (func)
- Enumerable.Aggregate (Seed, func)
- Enumerable.Aggregate (Seed, func, resultSelector)
- Enumerable.All (Prädikat)
- Enumerable.Any ()
- Enumerable.Any (Prädikat)
- Enumerable.AsEnumerable ()
- Aufzählungszeichen. Durchschnitt ()
- Enumerable.Average (Auswahl)
- Enumerable.Cast <Ergebnis> ()
- Enumerable.Concat (Sekunde)
- Enumerable.Contains (Wert)
- Enumerable.Contains (Wert, Vergleicher)
- Enumerable.Count ()
- Enumerable.Count (Prädikat)
- Enumerable.DefaultIfEmpty ()
- Enumerable.DefaultIfEmpty (defaultValue)
- Enumerable.Distinct ()
- Enumerable.Distinct (Vergleicher)
- Enumerable.ElementAt (Index)
- Enumerable.ElementAtOrDefault (Index)
- Enumerable.Empty ()
- Enumerable.Except (Sekunde)
- Enumerable.Except (zweiter Vergleicher)
- Enumerable.First ()
- Enumerable.First (Prädikat)
- Enumerable.FirstOrDefault ()
- Enumerable.FirstOrDefault (Prädikat)
- Enumerable.GroupBy (keySelector)
- Enumerable.GroupBy (keySelector, resultSelector)
- Enumerable.GroupBy (keySelector, elementSelector)
- Enumerable.GroupBy (keySelector, Vergleicher)
- Enumerable.GroupBy (keySelector, resultSelector, vergleicher)
- Enumerable.GroupBy (keySelector, elementSelector, resultSelector)
- Enumerable.GroupBy (keySelector, elementSelector, Vergleicher)
- Enumerable.GroupBy (keySelector, elementSelector, resultSelector, Vergleicher)
- Enumerable.Intersect (Sekunde)
- Enumerable.Intersect (zweiter Vergleicher)
- Enumerable.Join (inner, outerKeySelector, innerKeySelector, resultSelector)
- Enumerable.Join (inner, outerKeySelector, innerKeySelector, resultSelector, Vergleicher)
- Enumerable.Last ()
- Enumerable.Last (Prädikat)
- Enumerable.LastOrDefault ()
- Enumerable.LastOrDefault (Prädikat)
- Enumerable.LongCount ()
- Enumerable.LongCount (Prädikat)
- Enumerable.Max ()
- Enumerable.Max (Auswahl)
- Enumerable.Min ()
- Enumerable.Min (Auswahl)
- Enumerable.OfType <TResult> ()
- Enumerable.OrderBy (keySelector)
- Enumerable.OrderBy (keySelector, Vergleicher)
- Enumerable.OrderByDescending (keySelector)
- Enumerable.OrderByDescending (keySelector, Vergleicher)
- Enumerable.Range (Start, Anzahl)
- Enumerable.Repeat (Element, Anzahl)
- Enumerable.Reverse ()
- Enumerable.Select (Selektor)
- Enumerable.SelectMany (Selektor)
- Enumerable.SelectMany (collectionSelector, resultSelector)
- Enumerable.SequenceEqual (second)
- Enumerable.SequenceEqual (zweiter Vergleicher)
- Enumerable.Single ()
- Enumerable.Single (Prädikat)
- Enumerable.SingleOrDefault ()
- Enumerable.SingleOrDefault (Prädikat)
- Enumerable.Skip (Anzahl)
- Enumerable.SkipWhile (Prädikat)
- Enumerable.Sum ()
- Enumerable.Sum (Selektor)
- Enumerable.Take (Anzahl)
- Enumerable.TakeWhile (Prädikat)
- orderEnumerable.ThenBy (keySelector)
- orderEnumerable.ThenBy (keySelector, comparer)
- orderEnumerable.ThenByDescending (keySelector)
- orderEnumerable.ThenByDescending (keySelector, comparer)
- Enumerable.ToArray ()
- Enumerable.ToDictionary (keySelector)
- Enumerable.ToDictionary (keySelector, elementSelector)
- Enumerable.ToDictionary (keySelector, Vergleicher)
- Enumerable.ToDictionary (keySelector, elementSelector, comparer)
- Enumerable.ToList ()
- Enumerable.ToLookup (keySelector)
- Enumerable.ToLookup (keySelector, elementSelector)
- Enumerable.ToLookup (keySelector, Vergleicher)
- Enumerable.ToLookup (keySelector, elementSelector, Vergleicher)
- Enumerable.Union (Sekunde)
- Enumerable.Union (zweiter Vergleicher)
- Enumerable.Where (Prädikat)
- Enumerable.Zip (zweitens resultSelector)
Bemerkungen
Um LINQ-Abfragen verwenden zu können, müssen Sie System.Linq
importieren.
Die Methodensyntax ist leistungsfähiger und flexibler, aber die Abfragesyntax ist möglicherweise einfacher und bekannter. Alle in der Abfragesyntax geschriebenen Abfragen werden vom Compiler in die funktionale Syntax übersetzt. Die Leistung ist also gleich.
Abfrageobjekte werden erst ausgewertet, wenn sie verwendet werden. Sie können also ohne Leistungseinbußen geändert oder hinzugefügt werden.
Woher
Gibt eine Teilmenge von Elementen zurück, für die das angegebene Prädikat wahr ist.
List<string> trees = new List<string>{ "Oak", "Birch", "Beech", "Elm", "Hazel", "Maple" };
Methodensyntax
// Select all trees with name of length 3
var shortTrees = trees.Where(tree => tree.Length == 3); // Oak, Elm
Abfragesyntax
var shortTrees = from tree in trees
where tree.Length == 3
select tree; // Oak, Elm
Wählen Sie - Elemente transformieren
Mit Select können Sie eine Transformation auf jedes Element in einer Datenstruktur anwenden, die IEnumerable implementiert.
Abrufen des ersten Zeichens jeder Zeichenfolge in der folgenden Liste:
List<String> trees = new List<String>{ "Oak", "Birch", "Beech", "Elm", "Hazel", "Maple" };
Verwenden der regulären (Lambda) Syntax
//The below select stament transforms each element in tree into its first character.
IEnumerable<String> initials = trees.Select(tree => tree.Substring(0, 1));
foreach (String initial in initials) {
System.Console.WriteLine(initial);
}
Ausgabe:
O
B
B
E
H
M
Verwenden der LINQ-Abfragesyntax
initials = from tree in trees
select tree.Substring(0, 1);
Verkettungsmethoden
Viele LINQ-Funktionen arbeiten sowohl mit einem IEnumerable<TSource>
als auch mit einem IEnumerable<TResult>
. Die Typparameter TSource
und TResult
können sich abhängig von der TResult
Methode und den an sie übergebenen Funktionen auf denselben Typ beziehen.
Einige Beispiele dafür sind
public static IEnumerable<TResult> Select<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, TResult> selector
)
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, int, bool> predicate
)
public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
this IEnumerable<TSource> source,
Func<TSource, TKey> keySelector
)
Bei einigen Methodenverkettungen kann es vorkommen, dass vor dem Fortfahren ein ganzer Satz bearbeitet werden muss. LINQ nutzt jedoch die verzögerte Ausführung, indem er Rendite-Return- MSDN verwendet , wodurch ein Enumerable und ein Enumerator hinter den Kulissen erstellt werden. Beim Verketten in LINQ wird im Wesentlichen ein Aufzählungszeichen (Iterator) für den ursprünglichen Satz erstellt, der zurückgestellt wird, bis er durch Aufzählung des Aufzählungszeichens verwirklicht wird .
Dadurch können diese Funktionen fließend in einem Wiki verkettet werden , wobei eine Funktion direkt auf das Ergebnis einer anderen wirken kann. Mit dieser Art von Code können viele sequenzbasierte Operationen in einer einzigen Anweisung ausgeführt werden.
Beispielsweise können Sie Select
, Where
und OrderBy
kombinieren, um eine Sequenz in einer einzelnen Anweisung zu transformieren, zu filtern und zu sortieren.
var someNumbers = { 4, 3, 2, 1 };
var processed = someNumbers
.Select(n => n * 2) // Multiply each number by 2
.Where(n => n != 6) // Keep all the results, except for 6
.OrderBy(n => n); // Sort in ascending order
Ausgabe:
2
4
8
Alle Funktionen, die den generischen IEnumerable<T>
-Typ erweitern und zurückgeben, können als verkettete Klauseln in einer einzelnen Anweisung verwendet werden. Diese Art der fließenden Programmierung ist leistungsstark und sollte bei der Erstellung eigener Erweiterungsmethoden berücksichtigt werden .
Bereich und Wiederholung
Mit den statischen Methoden Range
und Repeat
von Enumerable
können einfache Sequenzen generiert werden.
Angebot
Enumerable.Range()
generiert eine Folge von Ganzzahlen mit einem Startwert und einer Zählung.
// Generate a collection containing the numbers 1-100 ([1, 2, 3, ..., 98, 99, 100])
var range = Enumerable.Range(1,100);
Wiederholen
Enumerable.Repeat()
generiert eine Folge sich wiederholender Elemente, die einem Element und der Anzahl der erforderlichen Wiederholungen zugeordnet sind.
// Generate a collection containing "a", three times (["a","a","a"])
var repeatedValues = Enumerable.Repeat("a", 3);
Überspringen und nehmen
Die Skip-Methode gibt eine Auflistung zurück, die eine Anzahl von Elementen am Anfang der Quellauflistung ausschließt. Die Anzahl der ausgeschlossenen Elemente ist die als Argument angegebene Anzahl. Wenn die Sammlung weniger Elemente enthält als im Argument angegeben, wird eine leere Sammlung zurückgegeben.
Die Take-Methode gibt eine Auflistung zurück, die eine Reihe von Elementen vom Anfang der Quellauflistung enthält. Die Anzahl der enthaltenen Elemente ist die als Argument angegebene Anzahl. Wenn die Sammlung weniger Elemente enthält als im Argument angegeben, enthält die zurückgegebene Sammlung die gleichen Elemente wie die Quellensammlung.
var values = new [] { 5, 4, 3, 2, 1 };
var skipTwo = values.Skip(2); // { 3, 2, 1 }
var takeThree = values.Take(3); // { 5, 4, 3 }
var skipOneTakeTwo = values.Skip(1).Take(2); // { 4, 3 }
var takeZero = values.Take(0); // An IEnumerable<int> with 0 items
Skip und Take werden häufig zusammen verwendet, um Ergebnisse zu paginieren, zum Beispiel:
IEnumerable<T> GetPage<T>(IEnumerable<T> collection, int pageNumber, int resultsPerPage) {
int startIndex = (pageNumber - 1) * resultsPerPage;
return collection.Skip(startIndex).Take(resultsPerPage);
}
Warnung: LINQ to Entities unterstützt nur das Überspringen von bestellten Abfragen . Wenn Sie versuchen, Skip ohne Reihenfolge zu verwenden, erhalten Sie eine NotSupportedException mit der Meldung "Die Methode 'Skip' wird nur für sortierte Eingaben in LINQ to Entities unterstützt. Die Methode 'OrderBy' muss vor der Methode 'Skip' aufgerufen werden.
Zuerst FirstOrDefault, Last, LastOrDefault, Single und SingleOrDefault
Alle sechs Methoden geben einen einzelnen Wert des Sequenztyps zurück und können mit oder ohne Prädikat aufgerufen werden.
Abhängig von der Anzahl der Elemente, die dem predicate
oder, wenn kein predicate
ist, der Anzahl der Elemente in der Quellsequenz, verhalten sie sich wie folgt:
Zuerst()
- Gibt das erste Element einer Sequenz oder das erste Element zurück, das dem angegebenen
predicate
. - Wenn die Sequenz keine Elemente enthält, wird eine
InvalidOperationException
mit der folgenden Meldung ausgelöst: "Sequenz enthält keine Elemente". - Wenn die Sequenz keine Elemente enthält, die mit dem bereitgestellten
predicate
übereinstimmen, wird eineInvalidOperationException
mit der Nachricht "Sequenz enthält kein übereinstimmendes Element" ausgelöst.
Beispiel
// Returns "a":
new[] { "a" }.First();
// Returns "a":
new[] { "a", "b" }.First();
// Returns "b":
new[] { "a", "b" }.First(x => x.Equals("b"));
// Returns "ba":
new[] { "ba", "be" }.First(x => x.Contains("b"));
// Throws InvalidOperationException:
new[] { "ca", "ce" }.First(x => x.Contains("b"));
// Throws InvalidOperationException:
new string[0].First();
FirstOrDefault ()
- Gibt das erste Element einer Sequenz oder das erste Element zurück, das dem angegebenen
predicate
. - Wenn die Sequenz keine Elemente oder keine Elemente enthält, die dem angegebenen
predicate
, wird der Standardwert des Sequenztyps mitdefault(T)
.
Beispiel
// Returns "a":
new[] { "a" }.FirstOrDefault();
// Returns "a":
new[] { "a", "b" }.FirstOrDefault();
// Returns "b":
new[] { "a", "b" }.FirstOrDefault(x => x.Equals("b"));
// Returns "ba":
new[] { "ba", "be" }.FirstOrDefault(x => x.Contains("b"));
// Returns null:
new[] { "ca", "ce" }.FirstOrDefault(x => x.Contains("b"));
// Returns null:
new string[0].FirstOrDefault();
Zuletzt()
- Gibt das letzte Element einer Sequenz oder das letzte Element zurück, das mit dem angegebenen
predicate
übereinstimmt. - Wenn die Sequenz keine Elemente enthält, wird eine
InvalidOperationException
mit der Meldung "Sequenz enthält keine Elemente" ausgelöst. - Wenn die Sequenz keine Elemente enthält, die mit dem bereitgestellten
predicate
übereinstimmen, wird eineInvalidOperationException
mit der Nachricht "Sequenz enthält kein übereinstimmendes Element" ausgelöst.
Beispiel
// Returns "a":
new[] { "a" }.Last();
// Returns "b":
new[] { "a", "b" }.Last();
// Returns "a":
new[] { "a", "b" }.Last(x => x.Equals("a"));
// Returns "be":
new[] { "ba", "be" }.Last(x => x.Contains("b"));
// Throws InvalidOperationException:
new[] { "ca", "ce" }.Last(x => x.Contains("b"));
// Throws InvalidOperationException:
new string[0].Last();
LastOrDefault ()
- Gibt das letzte Element einer Sequenz oder das letzte Element zurück, das mit dem angegebenen
predicate
übereinstimmt. - Wenn die Sequenz keine Elemente oder keine Elemente enthält, die dem angegebenen
predicate
, wird der Standardwert des Sequenztyps mitdefault(T)
.
Beispiel
// Returns "a":
new[] { "a" }.LastOrDefault();
// Returns "b":
new[] { "a", "b" }.LastOrDefault();
// Returns "a":
new[] { "a", "b" }.LastOrDefault(x => x.Equals("a"));
// Returns "be":
new[] { "ba", "be" }.LastOrDefault(x => x.Contains("b"));
// Returns null:
new[] { "ca", "ce" }.LastOrDefault(x => x.Contains("b"));
// Returns null:
new string[0].LastOrDefault();
Single()
- Wenn die Sequenz genau ein Element oder genau ein Element enthält, das mit dem angegebenen
predicate
übereinstimmt, wird dieses Element zurückgegeben. - Wenn die Sequenz keine Elemente enthält oder keine Elemente, die dem angegebenen
predicate
, wird eineInvalidOperationException
mit der Nachricht "Sequenz enthält keine Elemente" ausgelöst. - Wenn die Sequenz mehr als ein Element oder mehr als ein Element enthält, das mit dem bereitgestellten
predicate
übereinstimmt, wird eineInvalidOperationException
mit der Meldung "Sequenz enthält mehr als ein Element" ausgelöst. - Hinweis: Um auszuwerten, ob die Sequenz genau ein Element enthält, müssen höchstens zwei Elemente aufgelistet werden.
Beispiel
// Returns "a":
new[] { "a" }.Single();
// Throws InvalidOperationException because sequence contains more than one element:
new[] { "a", "b" }.Single();
// Returns "b":
new[] { "a", "b" }.Single(x => x.Equals("b"));
// Throws InvalidOperationException:
new[] { "a", "b" }.Single(x => x.Equals("c"));
// Throws InvalidOperationException:
new string[0].Single();
// Throws InvalidOperationException because sequence contains more than one element:
new[] { "a", "a" }.Single();
SingleOrDefault ()
- Wenn die Sequenz genau ein Element oder genau ein Element enthält, das mit dem angegebenen
predicate
übereinstimmt, wird dieses Element zurückgegeben. - Wenn die Sequenz keine Elemente enthält oder keine Elemente, die dem angegebenen
predicate
, wirddefault(T)
zurückgegeben. - Wenn die Sequenz mehr als ein Element oder mehr als ein Element enthält, das mit dem bereitgestellten
predicate
übereinstimmt, wird eineInvalidOperationException
mit der Meldung "Sequenz enthält mehr als ein Element" ausgelöst. - Wenn die Sequenz keine Elemente enthält, die dem bereitgestellten
predicate
, wird der Standardwert des Sequenztyps mitdefault(T)
. - Hinweis: Um auszuwerten, ob die Sequenz genau ein Element enthält, müssen höchstens zwei Elemente aufgelistet werden.
Beispiel
// Returns "a":
new[] { "a" }.SingleOrDefault();
// returns "a"
new[] { "a", "b" }.SingleOrDefault(x => x == "a");
// Returns null:
new[] { "a", "b" }.SingleOrDefault(x => x == "c");
// Throws InvalidOperationException:
new[] { "a", "a" }.SingleOrDefault(x => x == "a");
// Throws InvalidOperationException:
new[] { "a", "b" }.SingleOrDefault();
// Returns null:
new string[0].SingleOrDefault();
Empfehlungen
Obwohl Sie
FirstOrDefault
,LastOrDefault
oderSingleOrDefault
, um zu prüfen, ob eine Sequenz Elemente enthält, sindAny
oderCount
zuverlässiger. Dies liegt daran, dass ein Rückgabewert vondefault(T)
aus einer dieser drei Methoden nicht beweist, dass die Sequenz leer ist, da der Wert des ersten / letzten / einzelnen Elements der Sequenz gleichermaßendefault(T)
Entscheiden Sie, welche Methode für Ihren Code am besten geeignet ist. Verwenden Sie beispielsweise
Single
nur dann, wenn Sie sicherstellen müssen, dass ein einzelnes Element in der Auflistung Ihrem Prädikat entspricht. Andernfalls verwenden SieFirst
. alsSingle
eine Ausnahme, wenn die Sequenz mehr als ein übereinstimmendes Element enthält. Dies gilt natürlich auch für die "* OrDefault" -Counterparts.In Bezug auf die Effizienz: Obwohl es oft angebracht ist, sicherzustellen, dass es nur ein Element (
Single
) oder entweder nur ein oder null (SingleOrDefault
)SingleOrDefault
, die von einer Abfrage zurückgegeben werden, erfordern beide Methoden mehr und oft die gesamte Sammlung der Auflistung geprüft werden, um sicherzustellen, dass keine zweite Übereinstimmung mit der Abfrage besteht. Dies unterscheidet sich beispielsweise vom Verhalten derFirst
Methode, die nach dem Finden der ersten Übereinstimmung erfüllt werden kann.
Außer
Die Except-Methode gibt die Menge der Elemente zurück, die in der ersten Sammlung enthalten sind, aber nicht in der zweiten. Der Standard- IEqualityComparer
wird verwendet, um die Elemente in den beiden Sätzen zu vergleichen. Es gibt eine Überladung, die einen IEqualityComparer
als Argument akzeptiert.
Beispiel:
int[] first = { 1, 2, 3, 4 };
int[] second = { 0, 2, 3, 5 };
IEnumerable<int> inFirstButNotInSecond = first.Except(second);
// inFirstButNotInSecond = { 1, 4 }
Ausgabe:
1
4
In diesem Fall .Except(second)
schließt in dem Array enthaltenen Elemente second
, nämlich 2 und 3 (0 und 5 sind nicht in der enthaltenen first
Array und werden übersprungen).
Beachten Sie, dass Except
impliziert Distinct
(dh es werden wiederholte Elemente entfernt). Zum Beispiel:
int[] third = { 1, 1, 1, 2, 3, 4 };
IEnumerable<int> inThirdButNotInSecond = third.Except(second);
// inThirdButNotInSecond = { 1, 4 }
Ausgabe:
1
4
In diesem Fall werden die Elemente 1 und 4 nur einmal zurückgegeben.
IEquatable
oder die Funktion eines IEqualityComparer
, können Sie die Elemente mit einer anderen Methode vergleichen. Beachten Sie, dass die GetHashCode
Methode auch überschrieben werden sollte, damit ein identischer Hashcode für ein object
, das gemäß der IEquatable
Implementierung identisch ist.
Beispiel mit IEquatable:
class Holiday : IEquatable<Holiday>
{
public string Name { get; set; }
public bool Equals(Holiday other)
{
return Name == other.Name;
}
// GetHashCode must return true whenever Equals returns true.
public override int GetHashCode()
{
//Get hash code for the Name field if it is not null.
return Name?.GetHashCode() ?? 0;
}
}
public class Program
{
public static void Main()
{
List<Holiday> holidayDifference = new List<Holiday>();
List<Holiday> remoteHolidays = new List<Holiday>
{
new Holiday { Name = "Xmas" },
new Holiday { Name = "Hanukkah" },
new Holiday { Name = "Ramadan" }
};
List<Holiday> localHolidays = new List<Holiday>
{
new Holiday { Name = "Xmas" },
new Holiday { Name = "Ramadan" }
};
holidayDifference = remoteHolidays
.Except(localHolidays)
.ToList();
holidayDifference.ForEach(x => Console.WriteLine(x.Name));
}
}
Ausgabe:
Chanukka
SelectMany: Reduzieren einer Sequenz von Sequenzen
var sequenceOfSequences = new [] { new [] { 1, 2, 3 }, new [] { 4, 5 }, new [] { 6 } };
var sequence = sequenceOfSequences.SelectMany(x => x);
// returns { 1, 2, 3, 4, 5, 6 }
Verwenden Sie SelectMany()
wenn Sie bereits eine Sequenz von Sequenzen haben oder erstellen, das Ergebnis jedoch als eine lange Sequenz erstellt werden soll.
In LINQ-Abfragesyntax:
var sequence = from subSequence in sequenceOfSequences
from item in subSequence
select item;
Wenn Sie über eine Sammlung von Sammlungen verfügen und gleichzeitig Daten aus der übergeordneten und SelectMany
Sammlung SelectMany
, ist dies auch mit SelectMany
möglich.
Definieren wir einfache Klassen
public class BlogPost
{
public int Id { get; set; }
public string Content { get; set; }
public List<Comment> Comments { get; set; }
}
public class Comment
{
public int Id { get; set; }
public string Content { get; set; }
}
Nehmen wir an, wir haben folgende Sammlung.
List<BlogPost> posts = new List<BlogPost>()
{
new BlogPost()
{
Id = 1,
Comments = new List<Comment>()
{
new Comment()
{
Id = 1,
Content = "It's really great!",
},
new Comment()
{
Id = 2,
Content = "Cool post!"
}
}
},
new BlogPost()
{
Id = 2,
Comments = new List<Comment>()
{
new Comment()
{
Id = 3,
Content = "I don't think you're right",
},
new Comment()
{
Id = 4,
Content = "This post is a complete nonsense"
}
}
}
};
Jetzt möchten wir Kommentare Content
zusammen mit der Id
von BlogPost
die diesem Kommentar zugeordnet ist. Dazu können wir eine entsprechende SelectMany
Überladung verwenden.
var commentsWithIds = posts.SelectMany(p => p.Comments, (post, comment) => new { PostId = post.Id, CommentContent = comment.Content });
Unsere commentsWithIds
sieht so aus
{
PostId = 1,
CommentContent = "It's really great!"
},
{
PostId = 1,
CommentContent = "Cool post!"
},
{
PostId = 2,
CommentContent = "I don't think you're right"
},
{
PostId = 2,
CommentContent = "This post is a complete nonsense"
}
SelectMany
Die SelectMany linq-Methode "flachtet" einen IEnumerable<IEnumerable<T>>
IEnumerable<T>
in einen IEnumerable<T>
. Alle T-Elemente innerhalb der IEnumerable
Instanzen, die im Quell- IEnumerable
sind, werden zu einem einzigen IEnumerable
.
var words = new [] { "a,b,c", "d,e", "f" };
var splitAndCombine = words.SelectMany(x => x.Split(','));
// returns { "a", "b", "c", "d", "e", "f" }
Wenn Sie eine Auswahlfunktion verwenden, die Eingabeelemente in Sequenzen umwandelt, werden die Elemente dieser Sequenzen nacheinander zurückgegeben.
Beachten Sie, dass im Gegensatz zu Select()
die Anzahl der Elemente in der Ausgabe nicht dieselbe sein muss wie in der Eingabe.
Mehr Praxisbeispiel
class School
{
public Student[] Students { get; set; }
}
class Student
{
public string Name { get; set; }
}
var schools = new [] {
new School(){ Students = new [] { new Student { Name="Bob"}, new Student { Name="Jack"} }},
new School(){ Students = new [] { new Student { Name="Jim"}, new Student { Name="John"} }}
};
var allStudents = schools.SelectMany(s=> s.Students);
foreach(var student in allStudents)
{
Console.WriteLine(student.Name);
}
Ausgabe:
Bob
Jack
Jim
John
Alles
All
wird verwendet, um zu überprüfen, ob alle Elemente einer Sammlung einer Bedingung entsprechen oder nicht.
siehe auch: .Any
1. Leerer Parameter
All : darf nicht mit leeren Parametern verwendet werden.
2. Lambda-Ausdruck als Parameter
All : Gibt " true
wenn alle Elemente der Collection den Lambda-Ausdruck erfüllen, und " false
:
var numbers = new List<int>(){ 1, 2, 3, 4, 5};
bool result = numbers.All(i => i < 10); // true
bool result = numbers.All(i => i >= 3); // false
3. Leere Sammlung
All : Gibt " true
wenn die Auflistung leer ist und ein Lambda-Ausdruck angegeben wird:
var numbers = new List<int>();
bool result = numbers.All(i => i >= 0); // true
Hinweis: All
stoppen die Iteration der Sammlung, sobald ein Element gefunden wird, das nicht der Bedingung entspricht. Dies bedeutet, dass die Sammlung nicht unbedingt vollständig aufgezählt wird. Es wird nur so weit aufgezählt, dass der erste Artikel nicht der Bedingung entspricht.
Abfragesammlung nach Typ / Cast-Elementen zum Typ
interface IFoo { }
class Foo : IFoo { }
class Bar : IFoo { }
var item0 = new Foo();
var item1 = new Foo();
var item2 = new Bar();
var item3 = new Bar();
var collection = new IFoo[] { item0, item1, item2, item3 };
OfType
var foos = collection.OfType<Foo>(); // result: IEnumerable<Foo> with item0 and item1
var bars = collection.OfType<Bar>(); // result: IEnumerable<Bar> item item2 and item3
var foosAndBars = collection.OfType<IFoo>(); // result: IEnumerable<IFoo> with all four items
Where
var foos = collection.Where(item => item is Foo); // result: IEnumerable<IFoo> with item0 and item1
var bars = collection.Where(item => item is Bar); // result: IEnumerable<IFoo> with item2 and item3
Cast
var bars = collection.Cast<Bar>(); // throws InvalidCastException on the 1st item
var foos = collection.Cast<Foo>(); // throws InvalidCastException on the 3rd item
var foosAndBars = collection.Cast<IFoo>(); // OK
Union
Führt zwei Sammlungen zusammen, um mithilfe des Standardgleichheitsvergleichs eine eigene Sammlung zu erstellen
int[] numbers1 = { 1, 2, 3 };
int[] numbers2 = { 2, 3, 4, 5 };
var allElement = numbers1.Union(numbers2); // AllElement now contains 1,2,3,4,5
VERBINDUNGEN
Joins werden verwendet, um verschiedene Listen oder Tabellen, die Daten enthalten, über einen gemeinsamen Schlüssel zu kombinieren.
Wie in SQL werden in LINQ die folgenden Arten von Joins unterstützt:
Inner, links, rechts, Flanke und Full Outer Joins.
Die folgenden zwei Listen werden in den folgenden Beispielen verwendet:
var first = new List<string>(){ "a","b","c"}; // Left data
var second = new List<string>(){ "a", "c", "d"}; // Right data
(Inner) Join
var result = from f in first
join s in second on f equals s
select new { f, s };
var result = first.Join(second,
f => f,
s => s,
(f, s) => new { f, s });
// Result: {"a","a"}
// {"c","c"}
Linke äußere Verbindung
var leftOuterJoin = from f in first
join s in second on f equals s into temp
from t in temp.DefaultIfEmpty()
select new { First = f, Second = t};
// Or can also do:
var leftOuterJoin = from f in first
from s in second.Where(x => x == f).DefaultIfEmpty()
select new { First = f, Second = s};
// Result: {"a","a"}
// {"b", null}
// {"c","c"}
// Left outer join method syntax
var leftOuterJoinFluentSyntax = first.GroupJoin(second,
f => f,
s => s,
(f, s) => new { First = f, Second = s })
.SelectMany(temp => temp.Second.DefaultIfEmpty(),
(f, s) => new { First = f.First, Second = s });
Rechter äußerer Join
var rightOuterJoin = from s in second
join f in first on s equals f into temp
from t in temp.DefaultIfEmpty()
select new {First=t,Second=s};
// Result: {"a","a"}
// {"c","c"}
// {null,"d"}
Cross Join
var CrossJoin = from f in first
from s in second
select new { f, s };
// Result: {"a","a"}
// {"a","c"}
// {"a","d"}
// {"b","a"}
// {"b","c"}
// {"b","d"}
// {"c","a"}
// {"c","c"}
// {"c","d"}
Voller äußerer Join
var fullOuterjoin = leftOuterJoin.Union(rightOuterJoin);
// Result: {"a","a"}
// {"b", null}
// {"c","c"}
// {null,"d"}
Praktisches Beispiel
Die obigen Beispiele haben eine einfache Datenstruktur, sodass Sie sich darauf konzentrieren können, die verschiedenen LINQ-Joins technisch zu verstehen. In der realen Welt würden Sie jedoch Tabellen mit Spalten haben, die Sie verknüpfen müssen.
Im folgenden Beispiel wird nur eine Klasse Region
verwendet. In Wirklichkeit würden Sie zwei oder mehr verschiedene Tabellen mit demselben Schlüssel verknüpfen (in diesem Beispiel werden die first
und die second
über die gemeinsame Schlüssel- ID
).
Beispiel: Betrachten Sie die folgende Datenstruktur:
public class Region
{
public Int32 ID;
public string RegionDescription;
public Region(Int32 pRegionID, string pRegionDescription=null)
{
ID = pRegionID; RegionDescription = pRegionDescription;
}
}
Bereiten Sie nun die Daten vor (dh füllen Sie sie mit Daten auf):
// Left data
var first = new List<Region>()
{ new Region(1), new Region(3), new Region(4) };
// Right data
var second = new List<Region>()
{
new Region(1, "Eastern"), new Region(2, "Western"),
new Region(3, "Northern"), new Region(4, "Southern")
};
Sie können in diesem Beispiel sehen , dass first
keine Region Beschreibungen enthält , damit Sie sie aus anschließen möchten second
. Dann würde der innere Join aussehen:
// do the inner join
var result = from f in first
join s in second on f.ID equals s.ID
select new { f.ID, s.RegionDescription };
// Result: {1,"Eastern"}
// {3, Northern}
// {4,"Southern"}
Dieses Ergebnis hat anonyme Objekte select new { f.ID, s.RegionDescription };
erstellt, was in Ordnung ist, aber wir haben bereits eine richtige Klasse erstellt - so können wir sie angeben: Anstelle von select new { f.ID, s.RegionDescription };
wir können " select new Region(f.ID, s.RegionDescription);
, die dieselben Daten zurückgeben, aber Objekte vom Typ Region
erstellen, wodurch die Kompatibilität mit den anderen Objekten erhalten bleibt.
Eindeutig
Gibt eindeutige Werte aus einem IEnumerable
. Die Eindeutigkeit wird mit dem Standardgleichheitsvergleicher ermittelt.
int[] array = { 1, 2, 3, 4, 2, 5, 3, 1, 2 };
var distinct = array.Distinct();
// distinct = { 1, 2, 3, 4, 5 }
Um einen benutzerdefinierten Datentyp vergleichen zu können, müssen Sie die IEquatable<T>
Schnittstelle IEquatable<T>
implementieren und GetHashCode
und Equals
Methoden für den Typ bereitstellen. Oder der Gleichheitsvergleicher kann außer Kraft gesetzt werden:
class SSNEqualityComparer : IEqualityComparer<Person> {
public bool Equals(Person a, Person b) => return a.SSN == b.SSN;
public int GetHashCode(Person p) => p.SSN;
}
List<Person> people;
distinct = people.Distinct(SSNEqualityComparer);
GroupBy ein oder mehrere Felder
Nehmen wir an, wir haben ein Filmmodell:
public class Film {
public string Title { get; set; }
public string Category { get; set; }
public int Year { get; set; }
}
Gruppieren nach Kategorie-Eigenschaft:
foreach (var grp in films.GroupBy(f => f.Category)) {
var groupCategory = grp.Key;
var numberOfFilmsInCategory = grp.Count();
}
Gruppieren nach Kategorie und Jahr:
foreach (var grp in films.GroupBy(f => new { Category = f.Category, Year = f.Year })) {
var groupCategory = grp.Key.Category;
var groupYear = grp.Key.Year;
var numberOfFilmsInCategory = grp.Count();
}
Verwendung von Range mit verschiedenen Linq-Methoden
Sie können die Enumerable-Klasse zusammen mit Linq-Abfragen verwenden, um Loops in Linq-One-Liners zu konvertieren.
Wählen Sie Beispiel
Gegenteil davon:
var asciiCharacters = new List<char>();
for (var x = 0; x < 256; x++)
{
asciiCharacters.Add((char)x);
}
Du kannst das:
var asciiCharacters = Enumerable.Range(0, 256).Select(a => (char) a);
Wo Beispiel
In diesem Beispiel werden 100 Zahlen generiert und sogar Zahlen extrahiert
var evenNumbers = Enumerable.Range(1, 100).Where(a => a % 2 == 0);
Abfrage Bestellung - OrderBy () ThenBy () OrderByDescending () ThenByDescending ()
string[] names= { "mark", "steve", "adam" };
Aufsteigend:
Abfragesyntax
var sortedNames =
from name in names
orderby name
select name;
Methodensyntax
var sortedNames = names.OrderBy(name => name);
SortierteNames enthält die Namen in der folgenden Reihenfolge: "adam", "mark", "steve"
Absteigend:
Abfragesyntax
var sortedNames =
from name in names
orderby name descending
select name;
Methodensyntax
var sortedNames = names.OrderByDescending(name => name);
SortierteNames enthält die Namen in der folgenden Reihenfolge: "Steve", "Mark", "Adam"
Nach mehreren Feldern bestellen
Person[] people =
{
new Person { FirstName = "Steve", LastName = "Collins", Age = 30},
new Person { FirstName = "Phil" , LastName = "Collins", Age = 28},
new Person { FirstName = "Adam" , LastName = "Ackerman", Age = 29},
new Person { FirstName = "Adam" , LastName = "Ackerman", Age = 15}
};
Abfragesyntax
var sortedPeople = from person in people
orderby person.LastName, person.FirstName, person.Age descending
select person;
Methodensyntax
sortedPeople = people.OrderBy(person => person.LastName)
.ThenBy(person => person.FirstName)
.ThenByDescending(person => person.Age);
Ergebnis
1. Adam Ackerman 29
2. Adam Ackerman 15
3. Phil Collins 28
4. Steve Collins 30
Grundlagen
LINQ ist für das Abfragen von Sammlungen (oder Arrays) von großem Nutzen.
Angenommen, die folgenden Beispieldaten:
var classroom = new Classroom
{
new Student { Name = "Alice", Grade = 97, HasSnack = true },
new Student { Name = "Bob", Grade = 82, HasSnack = false },
new Student { Name = "Jimmy", Grade = 71, HasSnack = true },
new Student { Name = "Greg", Grade = 90, HasSnack = false },
new Student { Name = "Joe", Grade = 59, HasSnack = false }
}
Wir können diese Daten mithilfe der LINQ-Syntax "abfragen". So können Sie beispielsweise alle Schüler abrufen, die heute einen Snack erhalten:
var studentsWithSnacks = from s in classroom.Students
where s.HasSnack
select s;
Oder um Schüler mit einer Note von 90 oder höher abzurufen und nur ihren Namen und nicht das vollständige Student
:
var topStudentNames = from s in classroom.Students
where s.Grade >= 90
select s.Name;
Die LINQ-Funktion besteht aus zwei Syntaxen, die die gleichen Funktionen ausführen, eine nahezu identische Leistung haben, jedoch sehr unterschiedlich geschrieben sind. Die Syntax im obigen Beispiel wird als Abfragesyntax bezeichnet . Das folgende Beispiel veranschaulicht jedoch die Methodensyntax . Die gleichen Daten werden wie im obigen Beispiel zurückgegeben. Die Abfrage ist jedoch anders.
var topStudentNames = classroom.Students
.Where(s => s.Grade >= 90)
.Select(s => s.Name);
Gruppiere nach
GroupBy ist eine einfache Möglichkeit, eine IEnumerable<T>
-Sammlung von Elementen in verschiedene Gruppen zu sortieren.
Einfaches Beispiel
In diesem ersten Beispiel erhalten wir zwei Gruppen, ungerade und gerade Elemente.
List<int> iList = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var grouped = iList.GroupBy(x => x % 2 == 0);
//Groups iList into odd [13579] and even[2468] items
foreach(var group in grouped)
{
foreach (int item in group)
{
Console.Write(item); // 135792468 (first odd then even)
}
}
Komplexeres Beispiel
Nehmen wir als Beispiel eine Liste von Personen nach Alter. Zuerst erstellen wir ein Personenobjekt mit zwei Eigenschaften, Name und Alter.
public class Person
{
public int Age {get; set;}
public string Name {get; set;}
}
Dann erstellen wir eine Musterliste von Personen mit verschiedenen Namen und Alter.
List<Person> people = new List<Person>();
people.Add(new Person{Age = 20, Name = "Mouse"});
people.Add(new Person{Age = 30, Name = "Neo"});
people.Add(new Person{Age = 40, Name = "Morpheus"});
people.Add(new Person{Age = 30, Name = "Trinity"});
people.Add(new Person{Age = 40, Name = "Dozer"});
people.Add(new Person{Age = 40, Name = "Smith"});
Dann erstellen wir eine LINQ-Abfrage, um die Liste der Personen nach Alter zu gruppieren.
var query = people.GroupBy(x => x.Age);
Auf diese Weise können wir das Alter für jede Gruppe sehen und eine Liste aller Personen in der Gruppe haben.
foreach(var result in query)
{
Console.WriteLine(result.Key);
foreach(var person in result)
Console.WriteLine(person.Name);
}
Daraus ergibt sich folgende Ausgabe:
20
Mouse
30
Neo
Trinity
40
Morpheus
Dozer
Smith
Sie können mit der Live-Demo auf .NET Fiddle spielen
Irgendein
Any
wird geprüft, ob ein Element einer Collection einer Bedingung entspricht oder nicht.
Siehe auch: .All , Any und FirstOrDefault: Best Practice
1. Leerer Parameter
Any : Gibt true
wenn die Collection Elemente enthält, und false
wenn die Collection leer ist:
var numbers = new List<int>();
bool result = numbers.Any(); // false
var numbers = new List<int>(){ 1, 2, 3, 4, 5};
bool result = numbers.Any(); //true
2. Lambda-Ausdruck als Parameter
Any : Gibt true
wenn die Auflistung ein oder mehrere Elemente enthält, die die Bedingung im Lambda-Ausdruck erfüllen:
var arrayOfStrings = new string[] { "a", "b", "c" };
arrayOfStrings.Any(item => item == "a"); // true
arrayOfStrings.Any(item => item == "d"); // false
3. Leere Sammlung
Any : Gibt false
wenn die Sammlung leer ist und ein Lambda-Ausdruck angegeben wird:
var numbers = new List<int>();
bool result = numbers.Any(i => i >= 0); // false
Hinweis: Any
stoppt die Iteration der Sammlung, sobald ein der Bedingung entsprechendes Element gefunden wird. Dies bedeutet, dass die Sammlung nicht unbedingt vollständig aufgezählt wird. Es wird nur so weit aufgezählt, dass der erste Artikel gefunden wird, der der Bedingung entspricht.
ToDictionary
Mit der ToDictionary()
LINQ-Methode kann eine Dictionary<TKey, TElement>
-Sammlung basierend auf einer bestimmten IEnumerable<T>
.
IEnumerable<User> users = GetUsers();
Dictionary<int, User> usersById = users.ToDictionary(x => x.Id);
In diesem Beispiel hat das einzige an ToDictionary
Argument den Typ Func<TSource, TKey>
, der den Schlüssel für jedes Element zurückgibt.
Dies ist eine prägnante Möglichkeit, die folgende Operation auszuführen:
Dictionary<int, User> usersById = new Dictionary<int User>();
foreach (User u in users)
{
usersById.Add(u.Id, u);
}
Sie können auch einen zweiten Parameter an die ToDictionary
Methode übergeben, der vom Typ Func<TSource, TElement>
und den für jeden Eintrag hinzuzufügenden Value
zurückgibt.
IEnumerable<User> users = GetUsers();
Dictionary<int, string> userNamesById = users.ToDictionary(x => x.Id, x => x.Name);
Es ist auch möglich, den IComparer
anzugeben, der zum Vergleich von Schlüsselwerten verwendet wird. Dies kann nützlich sein, wenn der Schlüssel eine Zeichenfolge ist und die Groß- / Kleinschreibung berücksichtigt werden soll.
IEnumerable<User> users = GetUsers();
Dictionary<string, User> usersByCaseInsenstiveName = users.ToDictionary(x => x.Name, StringComparer.InvariantCultureIgnoreCase);
var user1 = usersByCaseInsenstiveName["john"];
var user2 = usersByCaseInsenstiveName["JOHN"];
user1 == user2; // Returns true
Hinweis: Für die ToDictionary
Methode müssen alle Schlüssel eindeutig sein. Es dürfen keine doppelten Schlüssel vorhanden sein. Wenn ArgumentException: An item with the same key has already been added.
, wird eine Ausnahme ausgelöst: ArgumentException: An item with the same key has already been added.
Wenn Sie ein Szenario haben, in dem Sie wissen, dass Sie mehrere Elemente mit demselben Schlüssel haben werden, sollten Sie lieber ToLookup
verwenden.
Aggregat
Aggregate
Wendet eine Akkumulatorfunktion auf eine Sequenz an.
int[] intList = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sum = intList.Aggregate((prevSum, current) => prevSum + current);
// sum = 55
- Im ersten Schritt ist
prevSum = 1
- Am zweiten
prevSum = prevSum(at the first step) + 2
- Im i-ten Schritt
prevSum = prevSum(at the (i-1) step) + i-th element of the array
string[] stringList = { "Hello", "World", "!" };
string joinedString = stringList.Aggregate((prev, current) => prev + " " + current);
// joinedString = "Hello World !"
Bei einer zweiten Überladung von Aggregate
auch ein seed
Parameter empfangen, der der anfängliche Akkumulatorwert ist. Dies kann verwendet werden, um mehrere Bedingungen für eine Sammlung zu berechnen, ohne sie mehrmals zu durchlaufen.
List<int> items = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
Für die Sammlung von items
wollen wir berechnen
- Die Summe
.Count
- Die Anzahl der geraden Zahlen
- Sammle jeden weiteren Gegenstand ein
Mit Aggregate
kann man so vorgehen:
var result = items.Aggregate(new { Total = 0, Even = 0, FourthItems = new List<int>() },
(accumelative,item) =>
new {
Total = accumelative.Total + 1,
Even = accumelative.Even + (item % 2 == 0 ? 1 : 0),
FourthItems = (accumelative.Total + 1)%4 == 0 ?
new List<int>(accumelative.FourthItems) { item } :
accumelative.FourthItems
});
// Result:
// Total = 12
// Even = 6
// FourthItems = [4, 8, 12]
Beachten Sie, dass bei Verwendung eines anonymen Typs als Startwert ein neues Objekt für jedes Element instanziiert werden muss, da die Eigenschaften schreibgeschützt sind. Mit einer benutzerdefinierten Klasse kann man die Informationen einfach zuweisen und es ist keine new
erforderlich (nur wenn der anfängliche seed
angegeben wird)
Definieren einer Variablen in einer Linq-Abfrage (Schlüsselwort let)
Um eine Variable innerhalb eines linq-Ausdrucks zu definieren, können Sie das let- Schlüsselwort verwenden. Dies geschieht normalerweise, um die Ergebnisse von Zwischenabfragen zu speichern, zum Beispiel:
int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var aboveAverages = from number in numbers
let average = numbers.Average()
let nSquared = Math.Pow(number,2)
where nSquared > average
select number;
Console.WriteLine("The average of the numbers is {0}.", numbers.Average());
foreach (int n in aboveAverages)
{
Console.WriteLine("Query result includes number {0} with square of {1}.", n, Math.Pow(n,2));
}
Ausgabe:
Der Durchschnitt der Zahlen ist 4,5.
Das Abfrageergebnis enthält die Nummer 3 mit dem Quadrat von 9.
Das Abfrageergebnis enthält die Nummer 4 mit dem Quadrat von 16.
Das Abfrageergebnis enthält die Nummer 5 mit dem Quadrat von 25.
Das Abfrageergebnis enthält die Nummer 6 mit dem Quadrat von 36.
Das Abfrageergebnis enthält die Nummer 7 mit dem Quadrat von 49.
Das Abfrageergebnis enthält die Nummer 8 mit einem Quadrat von 64.
Das Abfrageergebnis enthält die Nummer 9 mit dem Quadrat von 81.
SkipWährend
SkipWhile()
wird verwendet, um Elemente bis zur ersten Nichtübereinstimmung auszuschließen.
int[] list = { 42, 42, 6, 6, 6, 42 };
var result = list.SkipWhile(i => i == 42);
// Result: 6, 6, 6, 42
DefaultIfEmpty
DefaultIfEmpty wird verwendet, um ein Standardelement zurückzugeben, wenn die Sequenz keine Elemente enthält. Dieses Element kann der Standard des Typs oder eine benutzerdefinierte Instanz dieses Typs sein. Beispiel:
var chars = new List<string>() { "a", "b", "c", "d" };
chars.DefaultIfEmpty("N/A").FirstOrDefault(); // returns "a";
chars.Where(str => str.Length > 1)
.DefaultIfEmpty("N/A").FirstOrDefault(); // return "N/A"
chars.Where(str => str.Length > 1)
.DefaultIfEmpty().First(); // returns null;
Verwendung in Left Joins :
Mit DefaultIfEmpty
der herkömmliche Linq-Join ein Standardobjekt zurückgeben, wenn keine Übereinstimmung gefunden wurde. So fungiert sie als linker Join von SQL. Beispiel:
var leftSequence = new List<int>() { 99, 100, 5, 20, 102, 105 };
var rightSequence = new List<char>() { 'a', 'b', 'c', 'i', 'd' };
var numbersAsChars = from l in leftSequence
join r in rightSequence
on l equals (int)r into leftJoin
from result in leftJoin.DefaultIfEmpty('?')
select new
{
Number = l,
Character = result
};
foreach(var item in numbersAsChars)
{
Console.WriteLine("Num = {0} ** Char = {1}", item.Number, item.Character);
}
ouput:
Num = 99 Char = c
Num = 100 Char = d
Num = 5 Char = ?
Num = 20 Char = ?
Num = 102 Char = ?
Num = 105 Char = i
DefaultIfEmpty
ein DefaultIfEmpty
verwendet wird (ohne einen Standardwert anzugeben) und dies zu keinen übereinstimmenden Elementen in der richtigen Sequenz führt, müssen Sie sicherstellen, dass das Objekt nicht null
bevor Sie auf seine Eigenschaften zugreifen. Andernfalls führt dies zu einer NullReferenceException
. Beispiel:
var leftSequence = new List<int> { 1, 2, 5 };
var rightSequence = new List<dynamic>()
{
new { Value = 1 },
new { Value = 2 },
new { Value = 3 },
new { Value = 4 },
};
var numbersAsChars = (from l in leftSequence
join r in rightSequence
on l equals r.Value into leftJoin
from result in leftJoin.DefaultIfEmpty()
select new
{
Left = l,
// 5 will not have a matching object in the right so result
// will be equal to null.
// To avoid an error use:
// - C# 6.0 or above - ?.
// - Under - result == null ? 0 : result.Value
Right = result?.Value
}).ToList();
SequenceEqual
SequenceEqual
wird verwendet, um zwei IEnumerable<T>
-Sequenzen miteinander zu vergleichen.
int[] a = new int[] {1, 2, 3};
int[] b = new int[] {1, 2, 3};
int[] c = new int[] {1, 3, 2};
bool returnsTrue = a.SequenceEqual(b);
bool returnsFalse = a.SequenceEqual(c);
Count und LongCount
Count
gibt die Anzahl der Elemente in einem IEnumerable<T>
. Count
stellt auch einen optionalen Prädikatparameter bereit, mit dem Sie die Elemente filtern können, die Sie zählen möchten.
int[] array = { 1, 2, 3, 4, 2, 5, 3, 1, 2 };
int n = array.Count(); // returns the number of elements in the array
int x = array.Count(i => i > 2); // returns the number of elements in the array greater than 2
LongCount
funktioniert genauso wie Count
, hat jedoch den Rückgabetyp long
und wird zum Zählen von IEnumerable<T>
-Sequenzen verwendet, die länger als int.MaxValue
int[] array = GetLargeArray();
long n = array.LongCount(); // returns the number of elements in the array
long x = array.LongCount(i => i > 100); // returns the number of elements in the array greater than 100
Inkrementelles Erstellen einer Abfrage
Da LINQ die verzögerte Ausführung verwendet , können wir ein Abfrageobjekt haben, das die Werte nicht tatsächlich enthält, aber bei der Auswertung die Werte zurückgibt. Auf diese Weise können wir die Abfrage basierend auf unserem Kontrollfluss dynamisch erstellen und auswerten, sobald wir fertig sind:
IEnumerable<VehicleModel> BuildQuery(int vehicleType, SearchModel search, int start = 1, int count = -1) {
IEnumerable<VehicleModel> query = _entities.Vehicles
.Where(x => x.Active && x.Type == vehicleType)
.Select(x => new VehicleModel {
Id = v.Id,
Year = v.Year,
Class = v.Class,
Make = v.Make,
Model = v.Model,
Cylinders = v.Cylinders ?? 0
});
Wir können Filter bedingt anwenden:
if (!search.Years.Contains("all", StringComparer.OrdinalIgnoreCase))
query = query.Where(v => search.Years.Contains(v.Year));
if (!search.Makes.Contains("all", StringComparer.OrdinalIgnoreCase)) {
query = query.Where(v => search.Makes.Contains(v.Make));
}
if (!search.Models.Contains("all", StringComparer.OrdinalIgnoreCase)) {
query = query.Where(v => search.Models.Contains(v.Model));
}
if (!search.Cylinders.Equals("all", StringComparer.OrdinalIgnoreCase)) {
decimal minCylinders = 0;
decimal maxCylinders = 0;
switch (search.Cylinders) {
case "2-4":
maxCylinders = 4;
break;
case "5-6":
minCylinders = 5;
maxCylinders = 6;
break;
case "8":
minCylinders = 8;
maxCylinders = 8;
break;
case "10+":
minCylinders = 10;
break;
}
if (minCylinders > 0) {
query = query.Where(v => v.Cylinders >= minCylinders);
}
if (maxCylinders > 0) {
query = query.Where(v => v.Cylinders <= maxCylinders);
}
}
Wir können der Abfrage eine Sortierreihenfolge basierend auf einer Bedingung hinzufügen:
switch (search.SortingColumn.ToLower()) {
case "make_model":
query = query.OrderBy(v => v.Make).ThenBy(v => v.Model);
break;
case "year":
query = query.OrderBy(v => v.Year);
break;
case "engine_size":
query = query.OrderBy(v => v.EngineSize).ThenBy(v => v.Cylinders);
break;
default:
query = query.OrderBy(v => v.Year); //The default sorting.
}
Unsere Abfrage kann so definiert werden, dass sie an einem bestimmten Punkt beginnt:
query = query.Skip(start - 1);
und definiert, um eine bestimmte Anzahl von Datensätzen zurückzugeben:
if (count > -1) {
query = query.Take(count);
}
return query;
}
Sobald wir das Abfrageobjekt haben, können wir die Ergebnisse mit einer foreach
Schleife oder einer der LINQ-Methoden auswerten, die eine Menge von Werten wie ToList
oder ToArray
:
SearchModel sm;
// populate the search model here
// ...
List<VehicleModel> list = BuildQuery(5, sm).ToList();
Postleitzahl
Die Zip
Erweiterungsmethode wirkt auf zwei Sammlungen. Es paart jedes Element in den beiden Reihen nach Position. Bei einer Func
Instanz verwenden wir Zip
, um Elemente aus den beiden C # -Sammlungen paarweise zu behandeln. Wenn sich die Serien in der Größe unterscheiden, werden die zusätzlichen Elemente der größeren Serie ignoriert.
Um ein Beispiel aus dem Buch "C # in a Nutshell" zu nehmen,
int[] numbers = { 3, 5, 7 };
string[] words = { "three", "five", "seven", "ignored" };
IEnumerable<string> zip = numbers.Zip(words, (n, w) => n + "=" + w);
Ausgabe:
3 = drei
5 = fünf
7 = sieben
GroupJoin mit Variable für den äußeren Bereich
Customer[] customers = Customers.ToArray();
Purchase[] purchases = Purchases.ToArray();
var groupJoinQuery =
from c in customers
join p in purchases on c.ID equals p.CustomerID
into custPurchases
select new
{
CustName = c.Name,
custPurchases
};
ElementAt und ElementAtOrDefault
ElementAt
gibt das Element am Index n
. Wenn sich n
nicht innerhalb des Aufzählungsbereichs befindet, wird eine ArgumentOutOfRangeException
.
int[] numbers = { 1, 2, 3, 4, 5 };
numbers.ElementAt(2); // 3
numbers.ElementAt(10); // throws ArgumentOutOfRangeException
ElementAtOrDefault
gibt das Element an Index n
. Wenn n
nicht innerhalb des Aufzählungsbereichs liegt, wird ein default(T)
.
int[] numbers = { 1, 2, 3, 4, 5 };
numbers.ElementAtOrDefault(2); // 3
numbers.ElementAtOrDefault(10); // 0 = default(int)
Sowohl ElementAt
als auch ElementAtOrDefault
sind optimiert, wenn die Quelle eine IList<T>
und in diesen Fällen die normale Indizierung verwendet wird.
Beachten Sie, dass für ElementAt
, wenn der bereitgestellte Index größer als IList<T>
, die Liste eine ArgumentOutOfRangeException
IList<T>
soll (technisch gesehen jedoch nicht garantiert).
Linq-Quantifizierer
Quantifiziereroperationen geben einen booleschen Wert zurück, wenn einige oder alle Elemente in einer Sequenz eine Bedingung erfüllen. In diesem Artikel werden einige allgemeine LINQ to Objects-Szenarien beschrieben, in denen wir diese Operatoren verwenden können. Es gibt drei Quantifizierervorgänge, die in LINQ verwendet werden können:
All
- Wird verwendet, um zu bestimmen, ob alle Elemente einer Sequenz eine Bedingung erfüllen. Z.B:
int[] array = { 10, 20, 30 };
// Are all elements >= 10? YES
array.All(element => element >= 10);
// Are all elements >= 20? NO
array.All(element => element >= 20);
// Are all elements < 40? YES
array.All(element => element < 40);
Any
- Any
verwendet, um zu bestimmen, ob Elemente in einer Sequenz eine Bedingung erfüllen. Z.B:
int[] query=new int[] { 2, 3, 4 }
query.Any (n => n == 3);
Contains
- Bestimmt, ob eine Sequenz ein bestimmtes Element enthält. Z.B:
//for int array
int[] query =new int[] { 1,2,3 };
query.Contains(1);
//for string array
string[] query={"Tom","grey"};
query.Contains("Tom");
//for a string
var stringValue="hello";
stringValue.Contains("h");
Mehrere Sequenzen verbinden
Betrachten Sie die Entitäten Customer
, Purchase
und PurchaseItem
wie folgt:
public class Customer
{
public string Id { get; set } // A unique Id that identifies customer
public string Name {get; set; }
}
public class Purchase
{
public string Id { get; set }
public string CustomerId {get; set; }
public string Description { get; set; }
}
public class PurchaseItem
{
public string Id { get; set }
public string PurchaseId {get; set; }
public string Detail { get; set; }
}
Betrachten Sie die folgenden Beispieldaten für die obigen Entitäten:
var customers = new List<Customer>()
{
new Customer() {
Id = Guid.NewGuid().ToString(),
Name = "Customer1"
},
new Customer() {
Id = Guid.NewGuid().ToString(),
Name = "Customer2"
}
};
var purchases = new List<Purchase>()
{
new Purchase() {
Id = Guid.NewGuid().ToString(),
CustomerId = customers[0].Id,
Description = "Customer1-Purchase1"
},
new Purchase() {
Id = Guid.NewGuid().ToString(),
CustomerId = customers[0].Id,
Description = "Customer1-Purchase2"
},
new Purchase() {
Id = Guid.NewGuid().ToString(),
CustomerId = customers[1].Id,
Description = "Customer2-Purchase1"
},
new Purchase() {
Id = Guid.NewGuid().ToString(),
CustomerId = customers[1].Id,
Description = "Customer2-Purchase2"
}
};
var purchaseItems = new List<PurchaseItem>()
{
new PurchaseItem() {
Id = Guid.NewGuid().ToString(),
PurchaseId= purchases[0].Id,
Detail = "Purchase1-PurchaseItem1"
},
new PurchaseItem() {
Id = Guid.NewGuid().ToString(),
PurchaseId= purchases[1].Id,
Detail = "Purchase2-PurchaseItem1"
},
new PurchaseItem() {
Id = Guid.NewGuid().ToString(),
PurchaseId= purchases[1].Id,
Detail = "Purchase2-PurchaseItem2"
},
new PurchaseItem() {
Id = Guid.NewGuid().ToString(),
PurchaseId= purchases[3].Id,
Detail = "Purchase3-PurchaseItem1"
}
};
Betrachten Sie nun die folgende linq-Abfrage:
var result = from c in customers
join p in purchases on c.Id equals p.CustomerId // first join
join pi in purchaseItems on p.Id equals pi.PurchaseId // second join
select new
{
c.Name, p.Description, pi.Detail
};
So geben Sie das Ergebnis der obigen Abfrage aus:
foreach(var resultItem in result)
{
Console.WriteLine($"{resultItem.Name}, {resultItem.Description}, {resultItem.Detail}");
}
Die Ausgabe der Abfrage wäre:
Kunde1, Kunde1-Kauf1, Kauf1-PurchaseItem1
Kunde1, Kunde1-Einkauf2, Einkauf2-Einkaufselement1
Kunde1, Kunde1-Kauf2, Kauf2-PurchaseItem2
Customer2, Customer2-Purchase2, Purchase3-PurchaseItem1
Zusammenfügen auf mehreren Schlüsseln
PropertyInfo[] stringProps = typeof (string).GetProperties();//string properties
PropertyInfo[] builderProps = typeof(StringBuilder).GetProperties();//stringbuilder properties
var query =
from s in stringProps
join b in builderProps
on new { s.Name, s.PropertyType } equals new { b.Name, b.PropertyType }
select new
{
s.Name,
s.PropertyType,
StringToken = s.MetadataToken,
StringBuilderToken = b.MetadataToken
};
Beachten Sie, dass anonyme Typen in der obigen join
dieselben Eigenschaften enthalten müssen, da Objekte nur dann als gleich betrachtet werden, wenn alle ihre Eigenschaften gleich sind. Andernfalls wird die Abfrage nicht kompiliert.
Wählen Sie mit Func Selector - Verwenden Sie diese Option, um die Rangfolge der Elemente abzurufen
Auf den Überlastungen der Select
Erweiterungsmethoden führt auch den index
des aktuellen Elements in der Sammlung seines select
hrsg. Dies sind einige Anwendungen davon.
Holen Sie sich die "Zeilennummer" der Artikel
var rowNumbers = collection.OrderBy(item => item.Property1)
.ThenBy(item => item.Property2)
.ThenByDescending(item => item.Property3)
.Select((item, index) => new { Item = item, RowNumber = index })
.ToList();
Liefert den Rang eines Gegenstandes innerhalb seiner Gruppe
var rankInGroup = collection.GroupBy(item => item.Property1)
.OrderBy(group => group.Key)
.SelectMany(group => group.OrderBy(item => item.Property2)
.ThenByDescending(item => item.Property3)
.Select((item, index) => new
{
Item = item,
RankInGroup = index
})).ToList();
Rufen Sie das Ranking von Gruppen ab (auch in Oracle als dense_rank bekannt).
var rankOfBelongingGroup = collection.GroupBy(item => item.Property1)
.OrderBy(group => group.Key)
.Select((group, index) => new
{
Items = group,
Rank = index
})
.SelectMany(v => v.Items, (s, i) => new
{
Item = i,
DenseRank = s.Rank
}).ToList();
Zum Testen können Sie Folgendes verwenden:
public class SomeObject
{
public int Property1 { get; set; }
public int Property2 { get; set; }
public int Property3 { get; set; }
public override string ToString()
{
return string.Join(", ", Property1, Property2, Property3);
}
}
Und Daten:
List<SomeObject> collection = new List<SomeObject>
{
new SomeObject { Property1 = 1, Property2 = 1, Property3 = 1},
new SomeObject { Property1 = 1, Property2 = 2, Property3 = 1},
new SomeObject { Property1 = 1, Property2 = 2, Property3 = 2},
new SomeObject { Property1 = 2, Property2 = 1, Property3 = 1},
new SomeObject { Property1 = 2, Property2 = 2, Property3 = 1},
new SomeObject { Property1 = 2, Property2 = 2, Property3 = 1},
new SomeObject { Property1 = 2, Property2 = 3, Property3 = 1}
};
TakeWhile
TakeWhile
gibt Elemente aus einer Sequenz zurück, solange die Bedingung erfüllt ist
int[] list = { 1, 10, 40, 50, 44, 70, 4 };
var result = list.TakeWhile(item => item < 50).ToList();
// result = { 1, 10, 40 }
Summe
Die Erweiterungsmethode Enumerable.Sum
berechnet die Summe der numerischen Werte.
Falls die Elemente der Sammlung selbst Zahlen sind, können Sie die Summe direkt berechnen.
int[] numbers = new int[] { 1, 4, 6 };
Console.WriteLine( numbers.Sum() ); //outputs 11
Wenn der Typ der Elemente ein komplexer Typ ist, können Sie einen Lambda-Ausdruck verwenden, um den Wert anzugeben, der berechnet werden soll:
var totalMonthlySalary = employees.Sum( employee => employee.MonthlySalary );
Die Summenerweiterungsmethode kann mit den folgenden Typen berechnen:
- Int32
- Int64
- Single
- Doppelt
- Dezimal
Wenn Ihre Sammlung nullfähige Typen enthält, können Sie den Nullkoaleszenzoperator verwenden, um einen Standardwert für Nullelemente festzulegen:
int?[] numbers = new int?[] { 1, null, 6 };
Console.WriteLine( numbers.Sum( number => number ?? 0 ) ); //outputs 7
Nachschlagen
ToLookup gibt eine Datenstruktur zurück, die die Indizierung ermöglicht. Es ist eine Erweiterungsmethode. Sie erzeugt eine ILookup-Instanz, die mit einer foreach-Schleife indiziert oder aufgezählt werden kann. Die Einträge werden bei jeder Taste zu Gruppierungen zusammengefasst. - dotnetperls
string[] array = { "one", "two", "three" };
//create lookup using string length as key
var lookup = array.ToLookup(item => item.Length);
//join the values whose lengths are 3
Console.WriteLine(string.Join(",",lookup[3]));
//output: one,two
Ein anderes Beispiel:
int[] array = { 1,2,3,4,5,6,7,8 };
//generate lookup for odd even numbers (keys will be 0 and 1)
var lookup = array.ToLookup(item => item % 2);
//print even numbers after joining
Console.WriteLine(string.Join(",",lookup[0]));
//output: 2,4,6,8
//print odd numbers after joining
Console.WriteLine(string.Join(",",lookup[1]));
//output: 1,3,5,7
Erstellen Sie Ihre eigenen Linq-Operatoren für IEnumerable
Eines der großen Dinge an Linq ist, dass es so einfach zu erweitern ist. Sie müssen lediglich eine Erweiterungsmethode erstellen , deren Argument IEnumerable<T>
.
public namespace MyNamespace
{
public static class LinqExtensions
{
public static IEnumerable<List<T>> Batch<T>(this IEnumerable<T> source, int batchSize)
{
var batch = new List<T>();
foreach (T item in source)
{
batch.Add(item);
if (batch.Count == batchSize)
{
yield return batch;
batch = new List<T>();
}
}
if (batch.Count > 0)
yield return batch;
}
}
}
In diesem Beispiel werden die Elemente in einem IEnumerable<T>
in Listen fester Größe aufgeteilt. Die letzte Liste enthält den Rest der Elemente. Beachten Sie, wie das Objekt , auf dem das Verlängerungsverfahren angewendet wird , wird in (Argument übergeben source
) als das anfängliche Argument die Verwendung this
Schlüsselwort. Dann wird das yield
Schlüsselwort verwendet, um das nächste Element im Ausgabe- IEnumerable<T>
bevor die Ausführung ab diesem Punkt fortgesetzt wird (siehe Yield-Schlüsselwort ).
Dieses Beispiel würde in Ihrem Code folgendermaßen verwendet:
//using MyNamespace;
var items = new List<int> { 2, 3, 4, 5, 6 };
foreach (List<int> sublist in items.Batch(3))
{
// do something
}
In der ersten Schleife wäre die Unterliste {2, 3, 4}
und in der zweiten {5, 6}
.
Benutzerdefinierte LinQ-Methoden können auch mit Standard-LinQ-Methoden kombiniert werden. z.B:
//using MyNamespace;
var result = Enumerable.Range(0, 13) // generate a list
.Where(x => x%2 == 0) // filter the list or do something other
.Batch(3) // call our extension method
.ToList() // call other standard methods
Diese Abfrage gibt gerade Zahlen zurück, die in Gruppen mit einer Größe von 3 gruppiert sind: {0, 2, 4}, {6, 8, 10}, {12}
Denken Sie daran, dass Sie using MyNamespace;
Zeile, um auf die Erweiterungsmethode zugreifen zu können.
Verwenden von SelectMany anstelle von verschachtelten Schleifen
Gegeben 2 Listen
var list1 = new List<string> { "a", "b", "c" };
var list2 = new List<string> { "1", "2", "3", "4" };
Wenn Sie alle Permutationen ausgeben möchten, können Sie verschachtelte Schleifen wie verwenden
var result = new List<string>();
foreach (var s1 in list1)
foreach (var s2 in list2)
result.Add($"{s1}{s2}");
Mit SelectMany können Sie dieselbe Operation wie ausführen
var result = list1.SelectMany(x => list2.Select(y => $"{x}{y}", x, y)).ToList();
Any and First (OrDefault) - Best Practice
Ich werde nicht erklären, was Any
und FirstOrDefault
, da es bereits zwei gute Beispiele dafür gibt. Weitere Informationen finden Sie unter Any und First, FirstOrDefault, Last, LastOrDefault, Single und SingleOrDefault .
Ein Muster, das ich oft im Code sehe, sollte vermieden werden
if (myEnumerable.Any(t=>t.Foo == "Bob"))
{
var myFoo = myEnumerable.First(t=>t.Foo == "Bob");
//Do stuff
}
Es könnte so effizienter geschrieben werden
var myFoo = myEnumerable.FirstOrDefault(t=>t.Foo == "Bob");
if (myFoo != null)
{
//Do stuff
}
Wenn Sie das zweite Beispiel verwenden, wird die Sammlung nur einmal durchsucht und ergibt dasselbe Ergebnis wie das erste. Die gleiche Idee kann auf Single
angewendet werden.
GroupBy-Summe und Anzahl
Nehmen wir eine Probestunde an:
public class Transaction
{
public string Category { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
}
Lassen Sie uns nun eine Liste von Transaktionen betrachten:
var transactions = new List<Transaction>
{
new Transaction { Category = "Saving Account", Amount = 56, Date = DateTime.Today.AddDays(1) },
new Transaction { Category = "Saving Account", Amount = 10, Date = DateTime.Today.AddDays(-10) },
new Transaction { Category = "Credit Card", Amount = 15, Date = DateTime.Today.AddDays(1) },
new Transaction { Category = "Credit Card", Amount = 56, Date = DateTime.Today },
new Transaction { Category = "Current Account", Amount = 100, Date = DateTime.Today.AddDays(5) },
};
Wenn Sie die kategoriebezogene Summe aus Betrag und Anzahl berechnen möchten, können Sie GroupBy folgendermaßen verwenden:
var summaryApproach1 = transactions.GroupBy(t => t.Category)
.Select(t => new
{
Category = t.Key,
Count = t.Count(),
Amount = t.Sum(ta => ta.Amount),
}).ToList();
Console.WriteLine("-- Summary: Approach 1 --");
summaryApproach1.ForEach(
row => Console.WriteLine($"Category: {row.Category}, Amount: {row.Amount}, Count: {row.Count}"));
Alternativ können Sie dies in einem Schritt tun:
var summaryApproach2 = transactions.GroupBy(t => t.Category, (key, t) =>
{
var transactionArray = t as Transaction[] ?? t.ToArray();
return new
{
Category = key,
Count = transactionArray.Length,
Amount = transactionArray.Sum(ta => ta.Amount),
};
}).ToList();
Console.WriteLine("-- Summary: Approach 2 --");
summaryApproach2.ForEach(
row => Console.WriteLine($"Category: {row.Category}, Amount: {row.Amount}, Count: {row.Count}"));
Die Ausgabe für beide obigen Abfragen wäre gleich:
Kategorie: Konto speichern, Betrag: 66, Anzahl: 2
Kategorie: Kreditkarte, Betrag: 71, Anzahl: 2
Kategorie: Laufendes Konto, Anzahl: 100, Anzahl: 1
Umkehren
- Kehrt die Reihenfolge der Elemente in einer Reihenfolge um.
- Wenn keine Elemente vorhanden sind, wird eine
ArgumentNullException: source is null.
Beispiel:
// Create an array.
int[] array = { 1, 2, 3, 4 }; //Output:
// Call reverse extension method on the array. //4
var reverse = array.Reverse(); //3
// Write contents of array to screen. //2
foreach (int value in reverse) //1
Console.WriteLine(value);
Beachten Sie, dass Reverse()
abhängig von der Kettenreihenfolge Ihrer LINQ-Anweisungen möglicherweise anders arbeitet.
//Create List of chars
List<int> integerlist = new List<int>() { 1, 2, 3, 4, 5, 6 };
//Reversing the list then taking the two first elements
IEnumerable<int> reverseFirst = integerlist.Reverse<int>().Take(2);
//Taking 2 elements and then reversing only thos two
IEnumerable<int> reverseLast = integerlist.Take(2).Reverse();
//reverseFirst output: 6, 5
//reverseLast output: 2, 1
Reverse () funktioniert, indem es alles puffert und dann rückwärts durchläuft, was nicht sehr effizient ist, aber OrderBy ist diesbezüglich auch nicht.
In LINQ-to-Objects gibt es Pufferoperationen (Reverse, OrderBy, GroupBy usw.) und Nichtpufferoperationen (Where, Take, Skip usw.).
Beispiel: Nichtpufferung Rückwärtsverlängerung
public static IEnumerable<T> Reverse<T>(this IList<T> list) {
for (int i = list.Count - 1; i >= 0; i--)
yield return list[i];
}
Diese Methode kann auf Probleme stoßen, wenn Sie die Liste während der Iteration ändern.
Aufzählung der Aufzählungszeichen
Die IEnumerable <T> -Schnittstelle ist die Basisschnittstelle für alle generischen Enumeratoren und ein wesentlicher Bestandteil des Verständnisses von LINQ. Im Kern repräsentiert es die Sequenz.
Diese zugrunde liegende Schnittstelle wird von allen generischen Sammlungen vererbt, z. B. Collection <T> , Array , List <T> , Dictionary <TKey, TValue> -Klasse und HashSet <T> .
Zusätzlich zur Darstellung der Sequenz muss jede Klasse, die von IEnumerable <T> erbt, einen IEnumerator <T> bereitstellen. Der Enumerator macht den Iterator für das Enumerable verfügbar, und diese beiden miteinander verbundenen Schnittstellen und Ideen sind die Quelle für das Sprichwort "Enumerate the Enumerable".
"Aufzählen des Aufzählers" ist ein wichtiger Satz. Das Aufzählbare ist einfach eine Struktur für die Wiederholung, es enthält keine materialisierten Objekte. Bei der Sortierung kann ein Aufzählungszeichen beispielsweise die Kriterien des Felds für die Sortierung enthalten. .OrderBy()
jedoch .OrderBy()
, wird ein IEnumerable <T> zurückgegeben, das nur wissen kann, wie es sortiert werden soll. Die Verwendung eines Aufrufs, der die Objekte materialisiert, wie beim Durchlaufen der Menge, wird als Aufzählung bezeichnet (z. B. .ToList()
). Der Aufzählungsprozess verwendet die aufzählbare Definition, wie die Reihe durchlaufen werden soll und die relevanten Objekte (in der Reihenfolge gefiltert, projiziert usw.) zurückgegeben werden.
Erst wenn das Aufzählungszeichen aufgezählt wurde, führt dies zur Materialisierung der Objekte. Dies ist der Zeitpunkt, zu dem Metriken wie Zeitkomplexität (wie lange sie in Bezug auf die Seriengröße benötigt wird) und räumliche Komplexität (wie viel Platz in Bezug auf die Seriengröße beansprucht wird) können gemessen werden.
Das Erstellen einer eigenen Klasse, die von IEnumerable <T> erbt, kann etwas kompliziert sein, abhängig von der zugrunde liegenden Reihe, die aufgezählt werden muss. Im Allgemeinen ist es am besten, eine der vorhandenen generischen Sammlungen zu verwenden. Das heißt, es ist auch möglich, von der IEnumerable <T> -Schnittstelle zu erben, ohne ein definiertes Array als zugrunde liegende Struktur zu haben.
Verwenden Sie beispielsweise die Fibonacci-Serie als zugrunde liegende Sequenz. Beachten Sie, dass mit dem Aufruf von Where
einfach ein IEnumerable
wird. Erst wenn ein Aufruf zur IEnumerable
der Aufzählungszeichen erfolgt, werden alle Werte IEnumerable
.
void Main()
{
Fibonacci Fibo = new Fibonacci();
IEnumerable<long> quadrillionplus = Fibo.Where(i => i > 1000000000000);
Console.WriteLine("Enumerable built");
Console.WriteLine(quadrillionplus.Take(2).Sum());
Console.WriteLine(quadrillionplus.Skip(2).First());
IEnumerable<long> fibMod612 = Fibo.OrderBy(i => i % 612);
Console.WriteLine("Enumerable built");
Console.WriteLine(fibMod612.First());//smallest divisible by 612
}
public class Fibonacci : IEnumerable<long>
{
private int max = 90;
//Enumerator called typically from foreach
public IEnumerator GetEnumerator() {
long n0 = 1;
long n1 = 1;
Console.WriteLine("Enumerating the Enumerable");
for(int i = 0; i < max; i++){
yield return n0+n1;
n1 += n0;
n0 = n1-n0;
}
}
//Enumerable called typically from linq
IEnumerator<long> IEnumerable<long>.GetEnumerator() {
long n0 = 1;
long n1 = 1;
Console.WriteLine("Enumerating the Enumerable");
for(int i = 0; i < max; i++){
yield return n0+n1;
n1 += n0;
n0 = n1-n0;
}
}
}
Ausgabe
Enumerable built
Enumerating the Enumerable
4052739537881
Enumerating the Enumerable
4052739537881
Enumerable built
Enumerating the Enumerable
14930352
Die Stärke des zweiten Satzes (des fibMod612) ist, dass, obwohl wir den Aufruf zur Bestellung unseres gesamten Satzes von Fibonacci-Zahlen gemacht haben, da nur ein Wert mit .First()
die Zeitkomplexität O (n) als nur 1 Wert war musste während der Ausführung des Bestellalgorithmus verglichen werden. Dies liegt daran, dass unser Enumerator nur nach einem Wert gefragt hat und die gesamte Aufzählung nicht materialisiert werden musste. Hätten wir .Take(5)
anstelle von .First()
der Enumerator nach 5 Werten gefragt, und höchstens 5 Werte müssten .First()
werden. Im Vergleich zur Notwendigkeit, einen ganzen Satz zu bestellen und dann die ersten 5 Werte zu übernehmen, spart das Prinzip viel Ausführungszeit und -raum.
Sortieren nach
Bestellt eine Sammlung mit einem angegebenen Wert.
Wenn der Wert eine Ganzzahl , ein Doppel- oder ein Gleitkommawert ist , beginnt er mit dem Minimalwert , was bedeutet, dass Sie zuerst die negativen Werte und dann die Nullwerte und danach die positiven Werte erhalten (siehe Beispiel 1).
Wenn man von einem char Um das Verfahren vergleicht den ASCII - Wert der Zeichen , die Sammlung zu sortieren (siehe Beispiel 2).
Wenn Sie Zeichenfolgen sortieren, vergleicht die OrderBy-Methode sie, indem Sie deren CultureInfo- Informationen betrachten, die jedoch normalerweise mit dem ersten Buchstaben des Alphabets beginnen (a, b, c ...).
Diese Art von Reihenfolge wird als aufsteigend bezeichnet, wenn Sie es andersherum wünschen, müssen Sie absteigend (siehe OrderByDescending).
Beispiel 1:
int[] numbers = {2, 1, 0, -1, -2};
IEnumerable<int> ascending = numbers.OrderBy(x => x);
// returns {-2, -1, 0, 1, 2}
Beispiel 2
char[] letters = {' ', '!', '?', '[', '{', '+', '1', '9', 'a', 'A', 'b', 'B', 'y', 'Y', 'z', 'Z'};
IEnumerable<char> ascending = letters.OrderBy(x => x);
// returns { ' ', '!', '+', '1', '9', '?', 'A', 'B', 'Y', 'Z', '[', 'a', 'b', 'y', 'z', '{' }
Beispiel:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
var people = new[]
{
new Person {Name = "Alice", Age = 25},
new Person {Name = "Bob", Age = 21},
new Person {Name = "Carol", Age = 43}
};
var youngestPerson = people.OrderBy(x => x.Age).First();
var name = youngestPerson.Name; // Bob
OrderByDescending
Bestellt eine Sammlung mit einem angegebenen Wert.
Wenn der Wert eine Ganzzahl , ein Doppel- oder ein Gleitkommawert ist , beginnt er mit dem Maximalwert , was bedeutet, dass Sie zuerst die positiven Werte und dann die Nullwerte und danach die negativen Werte erhalten.
Wenn man von einem char Um das Verfahren vergleicht den ASCII - Wert der Zeichen , die Sammlung zu sortieren (siehe Beispiel 2).
Wenn Sie Strings sortieren, vergleicht die OrderBy-Methode sie, indem Sie deren CultureInfo- Informationen betrachten , die normalerweise mit dem letzten Buchstaben des Alphabets beginnen (z, y, x, ...).
Diese Art von Reihenfolge wird als absteigend bezeichnet, wenn Sie es andersherum wünschen, müssen Sie aufsteigend (siehe OrderBy).
Beispiel 1:
int[] numbers = {-2, -1, 0, 1, 2};
IEnumerable<int> descending = numbers.OrderByDescending(x => x);
// returns {2, 1, 0, -1, -2}
Beispiel 2
char[] letters = {' ', '!', '?', '[', '{', '+', '1', '9', 'a', 'A', 'b', 'B', 'y', 'Y', 'z', 'Z'};
IEnumerable<char> descending = letters.OrderByDescending(x => x);
// returns { '{', 'z', 'y', 'b', 'a', '[', 'Z', 'Y', 'B', 'A', '?', '9', '1', '+', '!', ' ' }
Beispiel 3:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
var people = new[]
{
new Person {Name = "Alice", Age = 25},
new Person {Name = "Bob", Age = 21},
new Person {Name = "Carol", Age = 43}
};
var oldestPerson = people.OrderByDescending(x => x.Age).First();
var name = oldestPerson.Name; // Carol
Concat
Führt zwei Sammlungen zusammen (ohne Duplikate zu entfernen)
List<int> foo = new List<int> { 1, 2, 3 };
List<int> bar = new List<int> { 3, 4, 5 };
// Through Enumerable static class
var result = Enumerable.Concat(foo, bar).ToList(); // 1,2,3,3,4,5
// Through extension method
var result = foo.Concat(bar).ToList(); // 1,2,3,3,4,5
Enthält
MSDN:
Bestimmt anhand eines angegebenen
IEqualityComparer<T>
ob eine Sequenz ein bestimmtes Element enthält
List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
var result1 = numbers.Contains(4); // true
var result2 = numbers.Contains(8); // false
List<int> secondNumberCollection = new List<int> { 4, 5, 6, 7 };
// Note that can use the Intersect method in this case
var result3 = secondNumberCollection.Where(item => numbers.Contains(item)); // will be true only for 4,5
Verwenden eines benutzerdefinierten Objekts:
public class Person
{
public string Name { get; set; }
}
List<Person> objects = new List<Person>
{
new Person { Name = "Nikki"},
new Person { Name = "Gilad"},
new Person { Name = "Phil"},
new Person { Name = "John"}
};
//Using the Person's Equals method - override Equals() and GetHashCode() - otherwise it
//will compare by reference and result will be false
var result4 = objects.Contains(new Person { Name = "Phil" }); // true
Verwenden der Enumerable.Contains(value, comparer)
:
public class Compare : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.Name == y.Name;
}
public int GetHashCode(Person codeh)
{
return codeh.Name.GetHashCode();
}
}
var result5 = objects.Contains(new Person { Name = "Phil" }, new Compare()); // true
Eine intelligente Verwendung von Contains
darin, mehrere if
Klauseln für einen Contains
Aufruf zu ersetzen.
Also statt dies zu tun:
if(status == 1 || status == 3 || status == 4)
{
//Do some business operation
}
else
{
//Do something else
}
Mach das:
if(new int[] {1, 3, 4 }.Contains(status)
{
//Do some business operaion
}
else
{
//Do something else
}