C# Language
Запросы LINQ
Поиск…
Вступление
LINQ - это аббревиатура, обозначающая L anguage IN tegrated Q uery. Это концепция, которая объединяет язык запросов, предлагая согласованную модель для работы с данными в различных источниках и форматах данных; вы используете одни и те же базовые шаблоны кодирования для запроса и преобразования данных в документы XML, базы данных SQL, наборы данных ADO.NET, коллекции .NET и любой другой формат, для которого доступен поставщик LINQ.
Синтаксис
Синтаксис запроса:
- из <range variable> в <collection>
- [from <range variable> в <collection>, ...]
- <фильтр, объединение, группировка, агрегированные операторы, ...> <выражение lambda>
- <select или groupBy operator> <сформулировать результат>
Синтаксис метода:
- Enumerable.Aggregate (FUNC)
- Enumerable.Aggregate (seed, func)
- Enumerable.Aggregate (seed, func, resultSelector)
- Enumerable.All (предикат)
- Enumerable.Any ()
- Enumerable.Any (предикат)
- Enumerable.AsEnumerable ()
- Enumerable.Average ()
- Enumerable.Average (селектор)
- Enumerable.Cast <результат> ()
- Enumerable.Concat (второй)
- Enumerable.Contains (значение)
- Enumerable.Contains (значение, сравнение)
- Enumerable.Count ()
- Enumerable.Count (предикат)
- Enumerable.DefaultIfEmpty ()
- Enumerable.DefaultIfEmpty (DefaultValue)
- Enumerable.Distinct ()
- Enumerable.Distinct (Comparer)
- Enumerable.ElementAt (индекс)
- Enumerable.ElementAtOrDefault (индекс)
- Enumerable.Empty ()
- Enumerable.Except (второй)
- Enumerable.Except (второй, сравнительный)
- Enumerable.First ()
- Enumerable.First (предикат)
- Enumerable.FirstOrDefault ()
- Enumerable.FirstOrDefault (предикат)
- Enumerable.GroupBy (keySelector)
- Enumerable.GroupBy (keySelector, resultSelector)
- Enumerable.GroupBy (keySelector, elementSelector)
- Enumerable.GroupBy (keySelector, comparer)
- Enumerable.GroupBy (keySelector, resultSelector, comparer)
- Enumerable.GroupBy (keySelector, elementSelector, resultSelector)
- Enumerable.GroupBy (keySelector, elementSelector, comparer)
- Enumerable.GroupBy (keySelector, elementSelector, resultSelector, comparer)
- Enumerable.Intersect (второй)
- Enumerable.Intersect (второй, сравнительный)
- Enumerable.Join (внутренний, внешнийKeySelector, innerKeySelector, resultSelector)
- Enumerable.Join (внутренний, внешнийKeySelector, innerKeySelector, resultSelector, comparer)
- Enumerable.Last ()
- Enumerable.Last (предикат)
- Enumerable.LastOrDefault ()
- Enumerable.LastOrDefault (предикат)
- Enumerable.LongCount ()
- Enumerable.LongCount (предикат)
- Enumerable.Max ()
- Enumerable.Max (селектор)
- Enumerable.Min ()
- Enumerable.Min (селектор)
- Enumerable.OfType <TResult> ()
- Enumerable.OrderBy (keySelector)
- Enumerable.OrderBy (keySelector, comparer)
- Enumerable.OrderByDescending (keySelector)
- Enumerable.OrderByDescending (keySelector, comparer)
- Enumerable.Range (начало, количество)
- Enumerable.Repeat (элемент, счетчик)
- Enumerable.Reverse ()
- Enumerable.Select (селектор)
- Enumerable.SelectMany (селектор)
- Enumerable.SelectMany (collectionSelector, resultSelector)
- Enumerable.SequenceEqual (второй)
- Enumerable.SequenceEqual (второй, сравнительный)
- Enumerable.Single ()
- Enumerable.Single (предикат)
- Enumerable.SingleOrDefault ()
- Enumerable.SingleOrDefault (предикат)
- Enumerable.Skip (количество)
- Enumerable.SkipWhile (предикат)
- Enumerable.Sum ()
- Enumerable.Sum (селектор)
- Enumerable.Take (количество)
- Enumerable.TakeWhile (предикат)
- orderedEnumerable.ThenBy (keySelector)
- orderedEnumerable.ThenBy (keySelector, comparer)
- orderedEnumerable.ThenByDescending (keySelector)
- orderedEnumerable.ThenByDescending (keySelector, comparer)
- Enumerable.ToArray ()
- Enumerable.ToDictionary (keySelector)
- Enumerable.ToDictionary (keySelector, elementSelector)
- Enumerable.ToDictionary (keySelector, comparer)
- Enumerable.ToDictionary (keySelector, elementSelector, comparer)
- Enumerable.ToList ()
- Enumerable.ToLookup (keySelector)
- Enumerable.ToLookup (keySelector, elementSelector)
- Enumerable.ToLookup (keySelector, comparer)
- Enumerable.ToLookup (keySelector, elementSelector, comparer)
- Enumerable.Union (второй)
- Enumerable.Union (второй, сравнительный)
- Enumerable.Where (предикат)
- Enumerable.Zip (второй, resultSelector)
замечания
Чтобы использовать запросы LINQ, вам необходимо импортировать System.Linq
.
Синтаксис метода более мощный и гибкий, но Синтаксис запроса может быть более простым и более знакомым. Все запросы, написанные в синтаксисе Query, транслируются в функциональный синтаксис компилятором, поэтому производительность одинакова.
Объекты запроса не оцениваются до тех пор, пока они не будут использованы, поэтому их можно изменить или добавить без штрафа за производительность.
куда
Возвращает подмножество элементов, для которых указанный предикат является истинным.
List<string> trees = new List<string>{ "Oak", "Birch", "Beech", "Elm", "Hazel", "Maple" };
Синтаксис метода
// Select all trees with name of length 3
var shortTrees = trees.Where(tree => tree.Length == 3); // Oak, Elm
Синтаксис запроса
var shortTrees = from tree in trees
where tree.Length == 3
select tree; // Oak, Elm
Выбрать - Преобразование элементов
Select позволяет применить преобразование к каждому элементу в любой структуре данных, реализующей IEnumerable.
Получение первого символа каждой строки в следующем списке:
List<String> trees = new List<String>{ "Oak", "Birch", "Beech", "Elm", "Hazel", "Maple" };
Использование регулярного (лямбда) синтаксиса
//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);
}
Выход:
О
В
В
Е
ЧАС
M
Живая демонстрация на .NET скрипке
Использование LINQ Query Syntax
initials = from tree in trees
select tree.Substring(0, 1);
Цепочные методы
Многие функции LINQ работают на IEnumerable<TSource>
а также возвращают IEnumerable<TResult>
. Параметры типа TSource
и TResult
могут или не могут ссылаться на один и тот же тип в зависимости от рассматриваемого метода и любых переданных ему функций.
Вот несколько примеров этого:
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
)
В то время как для некоторых цепочек методов может потребоваться весь набор, который должен быть обработан до перехода, LINQ использует преимущество отложенного исполнения , используя MSDN возврата доходности, который создает перечислимый и перечислитель за кулисами. Процесс привязки в LINQ по существу создает перечислимый (итератор) для исходного набора, который откладывается, до тех пор, пока не будет осуществлено перечислимое перечисление .
Это позволяет этим функциям свободно подключаться wiki , где одна функция может действовать непосредственно на результат другого. Этот стиль кода может использоваться для выполнения многих операций, основанных на последовательностях, в одном выражении.
Например, можно комбинировать Select
, Where
и OrderBy
для преобразования, фильтрации и сортировки последовательности в одном выражении.
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
Выход:
2
4
8
Живая демонстрация на .NET скрипке
Любые функции, которые расширяют и возвращают общий тип IEnumerable<T>
могут быть использованы в виде цепочечных предложений в одном выражении. Этот стиль свободного программирования является мощным, и его следует учитывать при создании собственных методов расширения .
Диапазон и повторение
Статические методы Range
и Repeat
для Enumerable
могут использоваться для генерации простых последовательностей.
Спектр
Enumerable.Range()
генерирует последовательность целых чисел, заданную начальным значением и счетчиком.
// Generate a collection containing the numbers 1-100 ([1, 2, 3, ..., 98, 99, 100])
var range = Enumerable.Range(1,100);
Живая демонстрация на .NET скрипке
Повторение
Enumerable.Repeat()
генерирует последовательность повторяющихся элементов с заданным элементом и количеством требуемых повторений.
// Generate a collection containing "a", three times (["a","a","a"])
var repeatedValues = Enumerable.Repeat("a", 3);
Живая демонстрация на .NET скрипке
Пропустить и принять
Метод Skip возвращает коллекцию, исключая количество элементов из начала исходной коллекции. Количество исключенных элементов - это число, указанное в качестве аргумента. Если в коллекции меньше элементов, чем указано в аргументе, возвращается пустая коллекция.
Метод Take возвращает коллекцию, содержащую несколько элементов из начала исходной коллекции. Количество включенных элементов - это число, указанное в качестве аргумента. Если в коллекции меньше элементов, чем указано в аргументе, то возвращенная коллекция будет содержать те же элементы, что и исходная коллекция.
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
Живая демонстрация на .NET скрипке
Skip и Take обычно используются вместе для получения результатов с разбивкой по страницам, например:
IEnumerable<T> GetPage<T>(IEnumerable<T> collection, int pageNumber, int resultsPerPage) {
int startIndex = (pageNumber - 1) * resultsPerPage;
return collection.Skip(startIndex).Take(resultsPerPage);
}
Предупреждение: LINQ to Entities поддерживает только пропущенные запросы . Если вы попытаетесь использовать Skip без заказа, вы получите NotSupportedException с сообщением «Метод« Пропустить »поддерживается только для отсортированного ввода в LINQ to Entities. Метод« OrderBy »должен быть вызван перед методом« Пропустить ».
Сначала FirstOrDefault, Last, LastOrDefault, Single и SingleOrDefault
Все шесть методов возвращают одно значение типа последовательности и могут быть вызваны с предикатом или без него.
В зависимости от количества элементов, соответствующих predicate
или, если predicate
не указан, количество элементов в исходной последовательности, они ведут себя следующим образом:
Первый()
- Возвращает первый элемент последовательности или первый элемент, соответствующий предоставленному
predicate
. - Если последовательность не содержит элементов, сообщение
InvalidOperationException
с сообщением: «Последовательность не содержит элементов». - Если последовательность не содержит элементов, соответствующих предоставленному
predicate
,InvalidOperationException
с сообщением «Последовательность не содержит соответствующего элемента».
пример
// 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();
Живая демонстрация на .NET скрипке
FirstOrDefault ()
- Возвращает первый элемент последовательности или первый элемент, соответствующий предоставленному
predicate
. - Если последовательность не содержит элементов или не содержит элементов, соответствующих предоставленному
predicate
, возвращает значение по умолчанию для типа последовательности, используяdefault(T)
поdefault(T)
.
пример
// 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();
Живая демонстрация на .NET скрипке
Прошлой()
- Возвращает последний элемент последовательности или последний элемент, соответствующий предоставленному
predicate
. - Если последовательность не содержит элементов,
InvalidOperationException
с сообщением «Последовательность не содержит элементов». - Если последовательность не содержит элементов, соответствующих предоставленному
predicate
,InvalidOperationException
с сообщением «Последовательность не содержит соответствующего элемента».
пример
// 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 ()
- Возвращает последний элемент последовательности или последний элемент, соответствующий предоставленному
predicate
. - Если последовательность не содержит элементов или не содержит элементов, соответствующих предоставленному
predicate
, возвращает значение по умолчанию для типа последовательности, используяdefault(T)
поdefault(T)
.
пример
// 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();
Не замужем()
- Если последовательность содержит ровно один элемент или ровно один элемент, соответствующий предоставленному
predicate
, этот элемент возвращается. - Если последовательность не содержит элементов или не содержит элементов, соответствующих предоставленному
predicate
,InvalidOperationException
с сообщением «Последовательность не содержит элементов». - Если последовательность содержит более одного элемента или более одного элемента, соответствующего предоставленному
predicate
,InvalidOperationException
с сообщением «Последовательность содержит более одного элемента». - Примечание. Чтобы оценить, содержит ли последовательность только один элемент, необходимо перечислить не более двух элементов.
пример
// 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 ()
- Если последовательность содержит ровно один элемент или ровно один элемент, соответствующий предоставленному
predicate
, этот элемент возвращается. - Если последовательность не содержит элементов или не содержит элементов, соответствующих предоставленному
predicate
, возвращаетсяdefault(T)
. - Если последовательность содержит более одного элемента или более одного элемента, соответствующего предоставленному
predicate
,InvalidOperationException
с сообщением «Последовательность содержит более одного элемента». - Если последовательность не содержит элементов, соответствующих предоставленному
predicate
, возвращает значение по умолчанию для типа последовательности, используяdefault(T)
поdefault(T)
. - Примечание. Чтобы оценить, содержит ли последовательность только один элемент, необходимо перечислить не более двух элементов.
пример
// 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();
рекомендации
Хотя вы можете использовать
FirstOrDefault
,LastOrDefault
илиSingleOrDefault
, чтобы проверить , содержит ли последовательность какие - либо элементы,Any
илиCount
являются более надежными. Это связано с тем, что возвращаемое значение поdefault(T)
из одного из этих трех методов не доказывает, что последовательность пуста, поскольку значение первого / последнего / единственного элемента последовательности может быть равно поdefault(T)
Решите, какие методы больше всего подходят для вашего кода. Например, используйте
Single
только в том случае, если вы должны убедиться, что в коллекции есть один элемент, соответствующий вашему предикату, в противном случае используйтеFirst
; какSingle
throw исключение, если последовательность имеет более одного соответствующего элемента. Это, конечно же, относится и к «* OrDefault» -конвертерам.Что касается эффективности: хотя часто бывает необходимо убедиться, что есть только один элемент (
Single
) или один или ноль (SingleOrDefault
), возвращаемый запросом, оба этих метода требуют больше, а часто и всей коллекции для проверки, чтобы гарантировать отсутствие второго ответа на запрос. Это не похоже на поведение, например, методаFirst
, который может быть удовлетворен после нахождения первого совпадения.
Кроме
Метод Except возвращает набор элементов, которые содержатся в первой коллекции, но не содержатся во втором. По умолчанию IEqualityComparer
используется для сравнения элементов в двух наборах. Существует перегрузка, которая воспринимает IEqualityComparer
как аргумент.
Пример:
int[] first = { 1, 2, 3, 4 };
int[] second = { 0, 2, 3, 5 };
IEnumerable<int> inFirstButNotInSecond = first.Except(second);
// inFirstButNotInSecond = { 1, 4 }
Выход:
1
4
Живая демонстрация на .NET скрипке
В этом случае. .Except(second)
исключает элементы, содержащиеся в массиве second
, а именно 2 и 3 (0 и 5 не содержатся в first
массиве и пропускаются).
Обратите внимание, что Except
означает Distinct
(т. Е. Удаляет повторяющиеся элементы). Например:
int[] third = { 1, 1, 1, 2, 3, 4 };
IEnumerable<int> inThirdButNotInSecond = third.Except(second);
// inThirdButNotInSecond = { 1, 4 }
Выход:
1
4
Живая демонстрация на .NET скрипке
В этом случае элементы 1 и 4 возвращаются только один раз.
Реализация IEquatable
или предоставление функции IEqualityComparer
позволит использовать другой метод для сравнения элементов. Обратите внимание, что метод GetHashCode
также должен быть переопределен, чтобы он возвращал идентичный хэш-код для object
который идентичен в соответствии с реализацией IEquatable
.
Пример с 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));
}
}
Выход:
ханука
Живая демонстрация на .NET скрипке
SelectMany: Сглаживание последовательности последовательностей
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 }
Используйте SelectMany()
если у вас есть, или вы создаете последовательность последовательностей, но вы хотите, чтобы результат был как одна длинная последовательность.
В LINQ Query Синтаксис:
var sequence = from subSequence in sequenceOfSequences
from item in subSequence
select item;
Если у вас есть коллекция коллекций и вы хотите одновременно работать с данными из родительской и дочерней коллекции, это также возможно с помощью SelectMany
.
Давайте определим простые классы
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; }
}
Предположим, что мы имеем следующую коллекцию.
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"
}
}
}
};
Теперь мы хотим выбрать комментарий Content
вместе с Id
BlogPost
связанным с этим комментарием. Для этого мы можем использовать соответствующую перегрузку SelectMany
.
var commentsWithIds = posts.SelectMany(p => p.Comments, (post, comment) => new { PostId = post.Id, CommentContent = comment.Content });
Наши commentsWithIds
выглядят так
{
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
Метод linq SelectMany «flattens» IEnumerable<IEnumerable<T>>
в IEnumerable<T>
. Все T-элементы внутри экземпляров IEnumerable
содержащиеся в исходном IEnumerable
будут объединены в один IEnumerable
.
var words = new [] { "a,b,c", "d,e", "f" };
var splitAndCombine = words.SelectMany(x => x.Split(','));
// returns { "a", "b", "c", "d", "e", "f" }
Если вы используете функцию селектора, которая превращает входные элементы в последовательности, результатом будут элементы тех последовательностей, которые возвращаются один за другим.
Обратите внимание, что, в отличие от Select()
, количество элементов на выходе не должно быть таким же, как на входе.
Более реальный пример
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);
}
Выход:
боб
Джек
Джим
Джон
Живая демонстрация на .NET скрипке
Все
All
используется для проверки, если все элементы коллекции соответствуют условию или нет.
Смотри также: .any
1. Пустой параметр
Все : не разрешено использовать с пустыми параметрами.
2. Лямбда-выражение как параметр
All : Возвращает true
если все элементы коллекции удовлетворяют лямбда-выражению и 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. Пустая коллекция
All : Возвращает true
если коллекция пуста и предоставляется выражение лямбда:
var numbers = new List<int>();
bool result = numbers.All(i => i >= 0); // true
Примечание. All
будут останавливать итерацию коллекции, как только она найдет элемент, не соответствующий условию. Это означает, что сбор не обязательно будет полностью перечислить; он будет только перечислить достаточно далеко, чтобы найти первый элемент, не соответствующий условию.
Сбор запросов по типу / литым элементам типа
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
союз
Объединение двух коллекций для создания отдельной коллекции с использованием сопоставителя равенства по умолчанию
int[] numbers1 = { 1, 2, 3 };
int[] numbers2 = { 2, 3, 4, 5 };
var allElement = numbers1.Union(numbers2); // AllElement now contains 1,2,3,4,5
Живая демонстрация на .NET скрипке
JOINS
Соединения используются для объединения разных списков или таблиц, содержащих данные с помощью общего ключа.
Как и в SQL, в LINQ поддерживаются следующие типы соединений:
Внутренний, левый, правый, крест и полный внешний вид .
В приведенных ниже примерах используются следующие два списка:
var first = new List<string>(){ "a","b","c"}; // Left data
var second = new List<string>(){ "a", "c", "d"}; // Right data
(Внутреннее соединение
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"}
Левое внешнее соединение
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 });
Правостороннее соединение
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"}
Крест
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"}
Полная внешняя связь
var fullOuterjoin = leftOuterJoin.Union(rightOuterJoin);
// Result: {"a","a"}
// {"b", null}
// {"c","c"}
// {null,"d"}
Практический пример
Приведенные выше примеры имеют простую структуру данных, поэтому вы можете сосредоточиться на понимании различных LINQ-соединений технически, но в реальном мире у вас будут таблицы с столбцами, которые вам нужно присоединиться.
В следующем примере используется только один класс Region
, на самом деле вы бы присоединились к двум или более различным таблицам, которые содержат один и тот же ключ (в этом примере first
и second
соединяются с помощью общего ID
ключа).
Пример. Рассмотрим следующую структуру данных:
public class Region
{
public Int32 ID;
public string RegionDescription;
public Region(Int32 pRegionID, string pRegionDescription=null)
{
ID = pRegionID; RegionDescription = pRegionDescription;
}
}
Теперь подготовьте данные (т.е. заполните данные):
// 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")
};
Вы можете видеть, что в этом примере first
не содержится описания региона, поэтому вы хотите присоединиться к ним со second
. Тогда внутреннее соединение будет выглядеть так:
// 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"}
Этот результат создал анонимные объекты «на лету», и это хорошо, но мы уже создали правильный класс, поэтому мы можем указать его: вместо select new { f.ID, s.RegionDescription };
мы можем сказать, select new Region(f.ID, s.RegionDescription);
, который вернет те же данные, но создаст объекты типа Region
-, которые будут поддерживать совместимость с другими объектами.
Живая демонстрация на .NET скрипке
отчетливый
Возвращает уникальные значения из IEnumerable
. Уникальность определяется с помощью сопоставления равенства по умолчанию.
int[] array = { 1, 2, 3, 4, 2, 5, 3, 1, 2 };
var distinct = array.Distinct();
// distinct = { 1, 2, 3, 4, 5 }
Чтобы сравнить пользовательский тип данных, нам нужно реализовать интерфейс IEquatable<T>
и предоставить методы GetHashCode
и Equals
для типа. Или сопоставитель равенства может быть переопределен:
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 одно или несколько полей
Предположим, что у нас есть модель фильма:
public class Film {
public string Title { get; set; }
public string Category { get; set; }
public int Year { get; set; }
}
Категория по категориям:
foreach (var grp in films.GroupBy(f => f.Category)) {
var groupCategory = grp.Key;
var numberOfFilmsInCategory = grp.Count();
}
Группа по категориям и годам:
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();
}
Использование Range с различными методами Linq
Вы можете использовать класс Enumerable вместе с запросами Linq, чтобы преобразовать для циклов в Linq один лайнер.
Выберите пример
Против этого:
var asciiCharacters = new List<char>();
for (var x = 0; x < 256; x++)
{
asciiCharacters.Add((char)x);
}
Вы можете сделать это:
var asciiCharacters = Enumerable.Range(0, 256).Select(a => (char) a);
Где пример
В этом примере будет создано 100 номеров, и даже те будут извлечены
var evenNumbers = Enumerable.Range(1, 100).Where(a => a % 2 == 0);
Запрос заказа - OrderBy () ThenBy () OrderByDescending () ThenByDescending ()
string[] names= { "mark", "steve", "adam" };
По возрастанию:
Синтаксис запроса
var sortedNames =
from name in names
orderby name
select name;
Синтаксис метода
var sortedNames = names.OrderBy(name => name);
sortedNames содержит имена в следующем порядке: «adam», «mark», «steve»
По убыванию:
Синтаксис запроса
var sortedNames =
from name in names
orderby name descending
select name;
Синтаксис метода
var sortedNames = names.OrderByDescending(name => name);
sortedNames содержит имена в следующем порядке: «steve», «mark», «adam»
Заказ по нескольким полям
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}
};
Синтаксис запроса
var sortedPeople = from person in people
orderby person.LastName, person.FirstName, person.Age descending
select person;
Синтаксис метода
sortedPeople = people.OrderBy(person => person.LastName)
.ThenBy(person => person.FirstName)
.ThenByDescending(person => person.Age);
Результат
1. Adam Ackerman 29
2. Adam Ackerman 15
3. Phil Collins 28
4. Steve Collins 30
основы
LINQ в значительной степени полезен для запросов к коллекциям (или массивам).
Например, учитывая следующие примеры данных:
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 }
}
Мы можем «запросить» эти данные с помощью синтаксиса LINQ. Например, чтобы получить всех студентов, которые сегодня перекусывают:
var studentsWithSnacks = from s in classroom.Students
where s.HasSnack
select s;
Или, чтобы получить студентов со степенью 90 или выше и только вернуть их имена, а не полный объект Student
:
var topStudentNames = from s in classroom.Students
where s.Grade >= 90
select s.Name;
Функция LINQ состоит из двух синтаксисов, которые выполняют одни и те же функции, имеют почти идентичную производительность, но написаны очень по-разному. Синтаксис в приведенном выше примере называется синтаксисом запроса . Следующий пример, однако, иллюстрирует синтаксис метода . Те же данные будут возвращены, как в приведенном выше примере, но способ записи запроса отличается.
var topStudentNames = classroom.Students
.Where(s => s.Grade >= 90)
.Select(s => s.Name);
Группа по
GroupBy - это простой способ сортировки коллекции элементов IEnumerable<T>
в отдельные группы.
Простой пример
В этом первом примере мы получаем две группы, нечетные и четные элементы.
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)
}
}
Более сложный пример
Давайте возьмем в качестве примера список людей по возрасту. Во-первых, мы создадим объект Person, который имеет два свойства: имя и возраст.
public class Person
{
public int Age {get; set;}
public string Name {get; set;}
}
Затем мы создаем наш образец списка людей с разными именами и возрастом.
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"});
Затем мы создаем запрос LINQ для группировки списка людей по возрасту.
var query = people.GroupBy(x => x.Age);
Поступая таким образом, мы можем видеть возраст для каждой группы и иметь список каждого человека в группе.
foreach(var result in query)
{
Console.WriteLine(result.Key);
foreach(var person in result)
Console.WriteLine(person.Name);
}
Это приводит к следующему результату:
20
Mouse
30
Neo
Trinity
40
Morpheus
Dozer
Smith
Вы можете играть с живой демонстрацией на .NET Fiddle
любой
Any
используется для проверки того, соответствует ли какой-либо элемент коллекции условию или нет.
Смотри также: .Все , Любой и FirstOrDefault: лучшая практика
1. Пустой параметр
Any : Возвращает true
если коллекция имеет какие-либо элементы и false
если коллекция пуста:
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. Лямбда-выражение как параметр
Any : Возвращает true
если коллекция имеет один или несколько элементов, удовлетворяющих условию в выражении лямбда:
var arrayOfStrings = new string[] { "a", "b", "c" };
arrayOfStrings.Any(item => item == "a"); // true
arrayOfStrings.Any(item => item == "d"); // false
3. Пустая коллекция
Any : Возвращает false
если коллекция пуста и предоставляется выражение лямбда:
var numbers = new List<int>();
bool result = numbers.Any(i => i >= 0); // false
Примечание. Any
остановит итерацию коллекции, как только найдет элемент, соответствующий условию. Это означает, что сбор не обязательно будет полностью перечислить; он будет только перечислить достаточно далеко, чтобы найти первый элемент, соответствующий условию.
Живая демонстрация на .NET скрипке
ToDictionary
Метод ToDictionary()
LINQ может использоваться для создания коллекции Dictionary<TKey, TElement>
на основе данного источника IEnumerable<T>
.
IEnumerable<User> users = GetUsers();
Dictionary<int, User> usersById = users.ToDictionary(x => x.Id);
В этом примере единственный аргумент, переданный ToDictionary
имеет тип Func<TSource, TKey>
, который возвращает ключ для каждого элемента.
Это краткий способ выполнить следующую операцию:
Dictionary<int, User> usersById = new Dictionary<int User>();
foreach (User u in users)
{
usersById.Add(u.Id, u);
}
Вы также можете передать второй параметр методу ToDictionary
, который имеет тип Func<TSource, TElement>
и возвращает Value
добавляемое для каждой записи.
IEnumerable<User> users = GetUsers();
Dictionary<int, string> userNamesById = users.ToDictionary(x => x.Id, x => x.Name);
Также можно указать IComparer
который используется для сравнения значений ключа. Это может быть полезно, когда ключ является строкой, и вы хотите, чтобы он соответствовал нечувствительности к регистру.
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
Примечание. Метод ToDictionary
требует, чтобы все ключи были уникальными, не должно быть дубликатов ключей. Если есть, то возникает исключение: ArgumentException: An item with the same key has already been added.
Если у вас есть сценарий, в котором вы знаете, что у вас будет несколько элементов с одним и тем же ключом, тогда вам лучше использовать ToLookup
.
заполнитель
Aggregate
Применяет функцию аккумулятора по последовательности.
int[] intList = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sum = intList.Aggregate((prevSum, current) => prevSum + current);
// sum = 55
- На первом этапе
prevSum = 1
- На втором
prevSum = prevSum(at the first step) + 2
- На i-м шаге
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 !"
Вторая перегрузка Aggregate
также получает параметр seed
который является начальным значением аккумулятора. Это можно использовать для вычисления нескольких условий в коллекции без повторения его более одного раза.
List<int> items = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
Для сбора items
мы хотим рассчитать
- Общее количество
.Count
- Количество четных чисел
- Соберите каждый четвертый пункт
Используя Aggregate
это можно сделать следующим образом:
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]
Обратите внимание, что использование анонимного типа в качестве семени должно создавать новый объект для каждого элемента, потому что свойства доступны только для чтения. Используя пользовательский класс, вы можете просто назначить информацию, а new
не нужен (только при задании начального параметра seed
Определение переменной внутри запроса Linq (пусть ключевое слово)
Чтобы определить переменную внутри выражения linq, вы можете использовать ключевое слово let . Обычно это делается для хранения результатов промежуточных подзапросов, например:
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));
}
Выход:
Среднее число - 4,5.
Результат запроса включает номер 3 с квадратом 9.
Результат запроса включает номер 4 с квадратом 16.
Результат запроса включает номер 5 с квадратом 25.
Результат запроса включает номер 6 с квадратом 36.
Результат запроса включает номер 7 с квадратом 49.
Результат запроса включает в себя номер 8 с квадратом 64.
Результат запроса включает номер 9 с квадратом 81.
SkipWhile
SkipWhile()
используется для исключения элементов до первого несоответствия (это может быть интуитивно понятным для большинства)
int[] list = { 42, 42, 6, 6, 6, 42 };
var result = list.SkipWhile(i => i == 42);
// Result: 6, 6, 6, 42
DefaultIfEmpty
DefaultIfEmpty используется для возврата элемента по умолчанию, если последовательность не содержит элементов. Этот Элемент может быть По умолчанию Тип или пользовательского экземпляра этого Типа. Пример:
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;
Использование в левых соединениях :
С DefaultIfEmpty
традиционная Linq Join может вернуть объект по умолчанию, если совпадение не найдено. Таким образом, выступая в качестве левого соединения SQL. Пример:
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
(без указания значения по умолчанию), и в результате не будет соответствующих элементов в правой последовательности, вы должны убедиться, что объект не имеет null
до доступа к его свойствам. В противном случае это приведет к NullReferenceException
. Пример:
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
используется для сравнения двух последовательностей IEnumerable<T>
друг с другом.
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 и LongCount
Count
возвращает количество элементов в IEnumerable<T>
. Count
также предоставляет необязательный параметр предиката, который позволяет вам фильтровать элементы, которые вы хотите подсчитать.
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
работает так же, как и Count
но имеет тип возврата long
и используется для подсчета последовательностей IEnumerable<T>
, которые длиннее 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
Поэтапное построение запроса
Поскольку LINQ использует отложенное выполнение , мы можем иметь объект запроса, который фактически не содержит значений, но возвращает значения при оценке. Таким образом, мы можем динамически строить запрос на основе нашего потока управления и оценивать его, как только мы закончим:
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
});
Мы можем условно применять фильтры:
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);
}
}
Мы можем добавить запрос сортировки к запросу на основе условия:
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.
}
Наш запрос может быть определен для начала из заданной точки:
query = query.Skip(start - 1);
и определен для возврата определенного количества записей:
if (count > -1) {
query = query.Take(count);
}
return query;
}
Когда у нас есть объект запроса, мы можем оценить результаты с помощью цикла foreach
или одного из методов LINQ, который возвращает набор значений, таких как ToList
или ToArray
:
SearchModel sm;
// populate the search model here
// ...
List<VehicleModel> list = BuildQuery(5, sm).ToList();
застежка-молния
Метод расширения Zip
действует на две коллекции. Он объединяет каждый элемент в двух сериях в зависимости от положения. С помощью экземпляра Func
мы используем Zip
для обработки элементов из двух коллекций C # в парах. Если серия отличается по размеру, дополнительные элементы большей серии будут игнорироваться.
Чтобы взять пример из книги «C # в двух словах»,
int[] numbers = { 3, 5, 7 };
string[] words = { "three", "five", "seven", "ignored" };
IEnumerable<string> zip = numbers.Zip(words, (n, w) => n + "=" + w);
Выход:
3 = три
5 = пять
7 = семь
GroupJoin с переменной внешней дальностью
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 и ElementAtOrDefault
ElementAt
вернет элемент с индексом n
. Если n
не входит в диапазон перечислимого, генерируется ArgumentOutOfRangeException
.
int[] numbers = { 1, 2, 3, 4, 5 };
numbers.ElementAt(2); // 3
numbers.ElementAt(10); // throws ArgumentOutOfRangeException
ElementAtOrDefault
вернет элемент с индексом n
. Если n
не входит в диапазон перечислимого, возвращает значение по default(T)
.
int[] numbers = { 1, 2, 3, 4, 5 };
numbers.ElementAtOrDefault(2); // 3
numbers.ElementAtOrDefault(10); // 0 = default(int)
И ElementAt
и ElementAtOrDefault
оптимизированы для того, когда источником является IList<T>
и нормальная индексация будет использоваться в этих случаях.
Обратите внимание, что для ElementAt
, если предоставленный индекс больше, чем размер IList<T>
, список должен (но технически не гарантирован) генерировать ArgumentOutOfRangeException
.
Линк-квантификаторы
Операции квантора возвращают логическое значение, если некоторые или все элементы в последовательности удовлетворяют условию. В этой статье мы увидим некоторые общие сценарии LINQ to Objects, где мы можем использовать эти операторы. В LINQ можно использовать 3 операции Quantifiers:
All
- используется для определения того, удовлетворяют ли все элементы в последовательности условию. Например:
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
- используется для определения того, удовлетворяют ли какие-либо элементы в последовательности условию. Например:
int[] query=new int[] { 2, 3, 4 }
query.Any (n => n == 3);
Contains
- используется для определения того, содержит ли последовательность указанный элемент. Например:
//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");
Объединение нескольких последовательностей
Рассмотрим объекты Customer
, Purchase
and PurchaseItem
следующим образом:
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; }
}
Рассмотрим следующие данные выборки для вышеуказанных объектов:
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"
}
};
Теперь рассмотрим ниже запрос linq:
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
};
Чтобы вывести результат вышеуказанного запроса:
foreach(var resultItem in result)
{
Console.WriteLine($"{resultItem.Name}, {resultItem.Description}, {resultItem.Detail}");
}
Результатом запроса будет:
Customer1, Customer1-Purchase1, Purchase1-PurchaseItem1
Customer1, Customer1-Purchase2, Purchase2-PurchaseItem1
Customer1, Customer1-Purchase2, Purchase2-PurchaseItem2
Customer2, Customer2-Purchase2, Purchase3-PurchaseItem1
Живая демонстрация на .NET скрипке
Присоединение к нескольким клавишам
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
};
Обратите внимание, что анонимные типы в join
выше должны содержать одинаковые свойства, поскольку объекты считаются равными, только если все их свойства равны. В противном случае запрос не будет компилироваться.
Выбрать с помощью Func selector - Использовать для ранжирования элементов
В случае перегрузок методов Select
также передается index
текущего элемента в select
коллекции. Это несколько его применений.
Получите «номер строки» элементов
var rowNumbers = collection.OrderBy(item => item.Property1)
.ThenBy(item => item.Property2)
.ThenByDescending(item => item.Property3)
.Select((item, index) => new { Item = item, RowNumber = index })
.ToList();
Получить ранг элемента в своей группе
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();
Получить рейтинг групп (также известный в Oracle как dense_rank)
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();
Для тестирования вы можете использовать:
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);
}
}
И данные:
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
возвращает элементы из последовательности, если условие истинно
int[] list = { 1, 10, 40, 50, 44, 70, 4 };
var result = list.TakeWhile(item => item < 50).ToList();
// result = { 1, 10, 40 }
сумма
Метод расширения Enumerable.Sum
вычисляет сумму числовых значений.
В случае, если элементы коллекции сами являются числами, вы можете рассчитать сумму напрямую.
int[] numbers = new int[] { 1, 4, 6 };
Console.WriteLine( numbers.Sum() ); //outputs 11
Если тип элементов является сложным типом, вы можете использовать выражение лямбда для указания значения, которое должно быть рассчитано:
var totalMonthlySalary = employees.Sum( employee => employee.MonthlySalary );
Метод расширения суммы может вычисляться со следующими типами:
- Int32
- Int64
- не замужем
- двойной
- Десятичный
Если ваша коллекция содержит типы с нулевым значением, вы можете использовать оператор null-coalescing для установки значения по умолчанию для нулевых элементов:
int?[] numbers = new int?[] { 1, null, 6 };
Console.WriteLine( numbers.Sum( number => number ?? 0 ) ); //outputs 7
ToLookup
ToLookup возвращает структуру данных, которая позволяет индексировать. Это метод расширения. Он создает экземпляр ILookup, который может быть проиндексирован или перечислен с использованием цикла foreach. Записи объединяются в группы по каждому ключу. - 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
Другой пример:
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
Создайте собственные операторы Linq для IEnumerable
Одна из замечательных особенностей Linq заключается в том, что ее так легко расширить. Вам просто нужно создать метод расширения , аргументом которого является 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;
}
}
}
Этот пример разбивает элементы в IEnumerable<T>
на списки фиксированного размера, последний список содержит оставшиеся элементы. Обратите внимание, как объект, к которому применяется метод расширения, передается в ( source
аргумента) в качестве исходного аргумента с использованием this
ключевого слова. Затем ключевое слово yield
используется для вывода следующего элемента в выходном IEnumerable<T>
прежде чем продолжить выполнение с этой точки (см. Ключевое слово yield ).
Этот пример будет использоваться в вашем коде следующим образом:
//using MyNamespace;
var items = new List<int> { 2, 3, 4, 5, 6 };
foreach (List<int> sublist in items.Batch(3))
{
// do something
}
В первом цикле подписок будет {2, 3, 4}
и вторым {5, 6}
.
Пользовательские методы LinQ можно комбинировать со стандартными методами LinQ. например:
//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
Этот запрос вернет четные числа, сгруппированные партиями с размером 3: {0, 2, 4}, {6, 8, 10}, {12}
Помните, что вам нужно using MyNamespace;
чтобы получить доступ к методу расширения.
Использование SelectMany вместо вложенных циклов
Учитывая 2 списка
var list1 = new List<string> { "a", "b", "c" };
var list2 = new List<string> { "1", "2", "3", "4" };
если вы хотите вывести все перестановки, вы можете использовать вложенные циклы, например
var result = new List<string>();
foreach (var s1 in list1)
foreach (var s2 in list2)
result.Add($"{s1}{s2}");
Используя SelectMany, вы можете выполнить ту же операцию, что и
var result = list1.SelectMany(x => list2.Select(y => $"{x}{y}", x, y)).ToList();
Любая и первая (OrDefault) - лучшая практика
Я не буду объяснять, что делает Any
и FirstOrDefault
, потому что в них уже есть два хороших примера. Для получения дополнительной информации см. Раздел « Все и первый», «FirstOrDefault», «Last», «LastOrDefault», «Single» и «SingleOrDefault» .
Я часто вижу код, который следует избегать ,
if (myEnumerable.Any(t=>t.Foo == "Bob"))
{
var myFoo = myEnumerable.First(t=>t.Foo == "Bob");
//Do stuff
}
Его можно было бы написать более эффективно
var myFoo = myEnumerable.FirstOrDefault(t=>t.Foo == "Bob");
if (myFoo != null)
{
//Do stuff
}
Используя второй пример, коллекция выполняется только один раз и дает тот же результат, что и первый. Та же идея может быть применена к Single
.
GroupBy Sum и Count
Возьмем образец класса:
public class Transaction
{
public string Category { get; set; }
public DateTime Date { get; set; }
public decimal Amount { get; set; }
}
Теперь рассмотрим список транзакций:
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) },
};
Если вы хотите рассчитать разумную сумму и количество баллов, вы можете использовать GroupBy следующим образом:
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}"));
Кроме того, вы можете сделать это за один шаг:
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}"));
Вывод для обоих вышеперечисленных запросов будет таким же:
Категория: Сберегательный счет, Сумма: 66, Количество: 2
Категория: Кредитная карточка, Сумма: 71, Количество: 2
Категория: Расчетный счет, Сумма: 100, Количество: 1
Задний ход
- Инвертирует порядок элементов в последовательности.
- Если нет элементов, вызывается
ArgumentNullException: source is null.
Пример:
// 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);
Помните, что Reverse()
может работать в зависимости от последовательности цепочек ваших операторов LINQ.
//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
Реверс () работает путем буферизации всего, а затем проходит через него назад, whitch не очень эффективен, но ни один OrderBy с этой точки зрения.
В LINQ-to-Objects выполняются операции буферизации (Reverse, OrderBy, GroupBy и т. Д.) И операции без буферизации (Where, Take, Skip и т. Д.).
Пример: Non-buffering Обратное расширение
public static IEnumerable<T> Reverse<T>(this IList<T> list) {
for (int i = list.Count - 1; i >= 0; i--)
yield return list[i];
}
Этот метод может столкнуться с проблемами, если и мутировать список во время итерации.
Перечисление Перечислимого
Интерфейс IEnumerable <T> является базовым интерфейсом для всех родовых счетчиков и является квинтэссенцией части понимания LINQ. По своей сути он представляет последовательность.
Этот базовый интерфейс наследуется всеми общими наборами, такими как Collection <T> , Array , List <T> , Dictionary <TKey, TValue> Class и HashSet <T> .
В дополнение к представлению последовательности любой класс, наследующий от IEnumerable <T>, должен предоставить IEnumerator <T>. Перечислитель предоставляет итератор для перечислимого, и эти два взаимосвязанных интерфейса и идеи являются источником высказывания «перечислять перечислимое».
«Перечисление перечислимого» - важная фраза. Перечислимый - это просто структура для итерации, она не содержит никаких материализованных объектов. Например, при сортировке перечислимый может содержать критерии поля для сортировки, но использование .OrderBy()
само по себе возвращает IEnumerable <T>, который знает только, как сортировать. Использование вызова, который материализует объекты, как итерации набора, называется перечислением (например .ToList()
). Процесс перечисления будет использовать перечисляемое определение того, как перемещаться по серии и возвращать соответствующие объекты (в порядке, отфильтровать, проецировать и т. Д.).
Только после перечислимого перечисления он вызывает материализацию объектов, когда метрики, такие как временная сложность (как долго это должно относиться к размеру сериала) и пространственная сложность (объем пространства, который он должен использовать, связанный с размером рядов), могут быть измеренным.
Создание собственного класса, наследующего от IEnumerable <T>, может быть немного сложным в зависимости от базовой серии, которая должна быть перечислимой. В общем, лучше всего использовать одну из существующих коллекций. Тем не менее, также можно наследовать от интерфейса IEnumerable <T>, не имея определенного массива в качестве базовой структуры.
Например, использование серии Фибоначчи в качестве базовой последовательности. Обратите внимание, что вызов Where
просто строит 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;
}
}
}
Выход
Enumerable built
Enumerating the Enumerable
4052739537881
Enumerating the Enumerable
4052739537881
Enumerable built
Enumerating the Enumerable
14930352
Сила во втором наборе (fibMod612) заключается в том, что, хотя мы сделали вызов упорядочить весь наш набор чисел Фибоначчи, поскольку только одно значение было принято с использованием .First()
сложность времени была O (n), поскольку только 1 значение необходимо было сравнить при выполнении алгоритма упорядочения. Это связано с тем, что наш счетчик запрашивал только 1 значение, поэтому весь перечислимый материал не должен был быть реализован. Если бы мы использовали .Take(5)
вместо .First()
перечислитель запросил бы 5 значений, и не более 5 значений должны были быть реализованы. По сравнению с необходимостью заказа всего набора, а затем принимать первые 5 значений, принцип сохраняет много времени и пространства выполнения.
Сортировать по
Заказывает коллекцию по заданному значению.
Когда значение представляет собой целое число , double или float начинается с минимального значения , а это означает, что вы сначала получаете отрицательные значения, чем ноль, а послесловия - положительные значения (см. Пример 1).
Когда вы заказываете char, метод сравнивает значения ascii символов для сортировки коллекции (см. Пример 2).
Когда вы сортируете строки, метод OrderBy сравнивает их, просматривая их CultureInfo, но нормально, начиная с первой буквы в алфавите (a, b, c ...).
Такой порядок называется восходящим, если вы хотите, чтобы он был наоборот, вам нужно спуститься (см. OrderByDescending).
Пример 1:
int[] numbers = {2, 1, 0, -1, -2};
IEnumerable<int> ascending = numbers.OrderBy(x => x);
// returns {-2, -1, 0, 1, 2}
Пример 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', '{' }
Пример:
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
Заказывает коллекцию по заданному значению.
Когда значение представляет собой целое число , double или float начинается с максимального значения , что означает, что вы сначала получаете положительные значения, а не 0, а послесловия - отрицательные значения (см. Пример 1).
Когда вы заказываете char, метод сравнивает значения ascii символов для сортировки коллекции (см. Пример 2).
Когда вы сортируете строки, метод OrderBy сравнивает их, рассматривая их CultureInfo, но нормально, начиная с последней буквы в алфавите (z, y, x, ...).
Такой порядок называется нисходящим, если вы хотите его наоборот, вам нужно подняться (см. OrderBy).
Пример 1:
int[] numbers = {-2, -1, 0, 1, 2};
IEnumerable<int> descending = numbers.OrderByDescending(x => x);
// returns {2, 1, 0, -1, -2}
Пример 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', '+', '!', ' ' }
Пример 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
Объединяет две коллекции (без удаления дубликатов)
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
Содержит
MSDN:
Определяет, содержит ли последовательность указанный элемент, используя указанный
IEqualityComparer<T>
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
Использование пользовательского объекта:
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
Использование перегрузки 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
Умное использование Contains
заключается в замене нескольких предложений if
на вызов Contains
.
Поэтому вместо этого:
if(status == 1 || status == 3 || status == 4)
{
//Do some business operation
}
else
{
//Do something else
}
Сделай это:
if(new int[] {1, 3, 4 }.Contains(status)
{
//Do some business operaion
}
else
{
//Do something else
}