الاستعلامات التكميلية اللغوية (Language Integrated Query - LINQ) هي تَعبيرات برمجية (expressions)، تَجلْب بيانات مُعينة من مَصدر بيانات (data source). تُوفِر استعلامات LINQ نَموذج مُتجانِس لتسهيل التعامل مع البيانات من مُختَلَف أنواع وصِيغْ مصادر البيانات (data sources). عند استخدامك لاستعلامات LINQ، فأنت دومًا تتعامل مع كائنات (objects)، وتَستَخدِم نفس الأنماط البرمجية الأساسية لجَلْب وتَغيير البيانات سواء كانت مُخزَّنة بملفات XML، أو بقواعد البيانات SQL، أو تقنية ADO.NET أو تَجمِيعَات .NET، أو أيّ صيغْْة أخرى مُتاح لها مُوفِّر (provider). يُمكن استخدام LINQ بلغتي C# و VB.
التابع SelectMany (ربط مسطح flat map)
لكل عنصر دخْل، يُعِيد التابع Enumerable.Select عنصر خْرج وحيد مُناظِر. في المقابل، يُسمَح للتابع Enumerable.SelectMany بإعادة أيّ عدد مُناظِر من عناصر الخْرج. بالتالي، قد يكون عدد عناصر مُتتالِية الخْرج مِن التابع SelectMany
غَيْر مُساو لعدد عناصر مُتتالية الدخْل.
تُمرَّر دالة مُجردة (Lambda expression) لكِلا التابعين، وينبغي لتلك الدالة إعادة عدد من القيم بما يتوافق مع ما ذُكِر بالأعلى.
مثال:
class Invoice { public int Id { get; set; } } class Customer { public Invoice[] Invoices {get;set;} } var customers = new[] { new Customer { Invoices = new[] { new Invoice {Id=1}, new Invoice {Id=2}, } }, new Customer { Invoices = new[] { new Invoice {Id=3}, new Invoice {Id=4}, } }, new Customer { Invoices = new[] { new Invoice {Id=5}, new Invoice {Id=6}, } } }; var allInvoicesFromAllCustomers = customers.SelectMany(c => c.Invoices); Console.WriteLine( string.Join(",", allInvoicesFromAllCustomers.Select(i => i.Id).ToArray()));
الخْرج:
1,2,3,4,5,6
يُمكن للاستعلام المُعتمِد على الصيغة (syntax-based query) تَنفيذ مُهمة التابع Enumerable.SelectMany
باستخدام عِبارتي from
متتاليتين كالتالي:
var allInvoicesFromAllCustomers = from customer in customers from invoice in customer.Invoices select invoice;
التابع Where (تَرشِيح filter)
يُعيد التابع Where
مُُعدَّد من الواجهة IEnumerable
، والذي يَحوِي جميع العناصر التي تُحقِق شرط الدالة المُجردة (lambda expression) المُمرَّرة.
مِثال:
var personNames = new[] { "Foo", "Bar", "Fizz", "Buzz" }; var namesStartingWithF = personNames.Where(p => p.StartsWith("F")); Console.WriteLine(string.Join(",", namesStartingWithF));
الخْرج:
Foo,Fizz
التابع Any
يُعيد التابع Any
القيمة المنطقية true
إذا حَوَت التَجمِيعَة أيّة عناصر تُحقِق شرط الدالة المُجردة (lambda expression) المُمرَّرة. انظر المثال التالي:
var numbers = new[] {1,2,3,4,5}; var isNotEmpty = numbers.Any(); Console.WriteLine(isNotEmpty); //True var anyNumberIsOne = numbers.Any(n => n == 1); Console.WriteLine(anyNumberIsOne); //True var anyNumberIsSix = numbers.Any(n => n == 6); Console.WriteLine(anyNumberIsSix); //False var anyNumberIsOdd = numbers.Any(n => (n & 1) == 1); Console.WriteLine(anyNumberIsOdd); //True var anyNumberIsNegative = numbers.Any(n => n < 0); Console.WriteLine(anyNumberIsNegative); //False
التابع GroupJoin
class Developer { public int Id { get; set; } public string Name { get; set; } } class Project { public int DeveloperId { get; set; } public string Name { get; set; } } var developers = new[] { new Developer { Id = 1, Name = "Foobuzz" }, new Developer { Id = 2, Name = "Barfizz" } }; var projects = new[] { new Project { DeveloperId = 1, Name = "Hello World 3D" }, new Project { DeveloperId = 1, Name = "Super Fizzbuzz Maker" }, new Project { DeveloperId = 2, Name = "Citizen Kane - The action game" }, new Project { DeveloperId = 2, Name = "Pro Pong 2016" } }; var grouped = developers.GroupJoin( inner: projects, outerKeySelector: dev => dev.Id, innerKeySelector: proj => proj.DeveloperId, resultSelector: (dev, projs) => new { DeveloperName = dev.Name, ProjectNames = projs.Select(p => p.Name).ToArray()}); foreach(var item in grouped) { Console.WriteLine( "{0}'s projects: {1}", item.DeveloperName, string.Join(", ", item.ProjectNames)); } //Foobuzz's projects: Hello World 3D, Super Fizzbuzz Maker //Barfizz's projects: Citizen Kane - The action game, Pro Pong 2016
التابع Except
var numbers = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; var evenNumbersBetweenSixAndFourteen = new[] { 6, 8, 10, 12 }; var result = numbers.Except(evenNumbersBetweenSixAndFourteen); Console.WriteLine(string.Join(",", result)); //1, 2, 3, 4, 5, 7, 9
التابع Zip
بِدءًا من الإصدار 4.0 أو أحدث.
var tens = new[] {10,20,30,40,50}; var units = new[] {1,2,3,4,5}; var sums = tens.Zip(units, (first, second) => first + second); Console.WriteLine(string.Join(",", sums)); //11,22,33,44,55
التابع Aggregate (طوي fold)
يَستقبِل التابع Aggregate
المُعامِل seed
، الذي يَحوِي قيمة التهيئة للمُراكِم (accumulator). في حالة تمرير سلسلة نصية كالمثال التالي، سيُنشَّئ كائن جديد مع كل خطوة أي بِعَدَد عناصر المُعدَّد.
var elements = new[] {1,2,3,4,5}; var commaSeparatedElements = elements.Aggregate( seed: "", func: (aggregate, element) => $"{aggregate}{element},"); Console.WriteLine(commaSeparatedElements); //1,2,3,4,5,
لاستخدام نفس الكائن بجميع الخطوات، مَرِّر كائنًا من النوع StringBuilder
للمُعامِل seed
كالتالي:
var commaSeparatedElements2 = elements.Aggregate( seed: new StringBuilder(), func: (seed, element) => seed.Append($"{element},")); Console.WriteLine(commaSeparatedElements2.ToString()); //1,2,3,4,5,
يَتحكَم المُعامِل resultSelector
بصيغة الخْرج:
var commaSeparatedElements3 = elements.Aggregate( seed: new StringBuilder(), func: (seed, element) => seed.Append($"{element},"), resultSelector: (seed) => seed.ToString()); Console.WriteLine(commaSeparatedElements3); //1,2,3,4,5,
إذا لم تُمَرَّر قيمة للمُعامِل seed
، تؤول قيمته بشكل افتراضي لأول عنصر بالمُعدَّد.
var seedAndElements = elements.Select(n=>n.ToString()); var commaSeparatedElements4 = seedAndElements.Aggregate( func: (aggregate, element) => $"{aggregate}{element},"); Console.WriteLine(commaSeparatedElements4); //12,3,4,5,
التابع ToLookup
var persons = new[] { new { Name="Fizz", Job="Developer"}, new { Name="Buzz", Job="Developer"}, new { Name="Foo", Job="Astronaut"}, new { Name="Bar", Job="Astronaut"}, }; var groupedByJob = persons.ToLookup(p => p.Job); foreach(var theGroup in groupedByJob) { Console.WriteLine( "{0} are {1}s", string.Join(",", theGroup.Select(g => g.Name).ToArray()), theGroup.Key); } //Fizz,Buzz are Developers //Foo,Bar are Astronauts
التابع Intersect
var numbers1to10 = new[] {1,2,3,4,5,6,7,8,9,10}; var numbers5to15 = new[] {5,6,7,8,9,10,11,12,13,14,15}; var numbers5to10 = numbers1to10.Intersect(numbers5to15); Console.WriteLine(string.Join(",", numbers5to10)); //5,6,7,8,9,10
التابع Concat
var numbers1to5 = new[] {1, 2, 3, 4, 5}; var numbers4to8 = new[] {4, 5, 6, 7, 8}; var numbers1to8 = numbers1to5.Concat(numbers4to8); Console.WriteLine(string.Join(",", numbers1to8)); //1,2,3,4,5,4,5,6,7,8
يحتفظ التابع Concat
بالعناصر المُكرّرة دون حَذفِها. اِستخدِم التابع Union
إذا لم يكن ذلك ما ترغب به.
التابع All
var numbers = new[] {1,2,3,4,5}; var allNumbersAreOdd = numbers.All(n => (n & 1) == 1); Console.WriteLine(allNumbersAreOdd); //False var allNumbersArePositive = numbers.All(n => n > 0); Console.WriteLine(allNumbersArePositive); //True
يُعيِد التابع All
القيمة المنطقية false
فقط عندما يجد عنصرًا بالمُعدَّد لا يُحقق شرط الدالة المُجردة (lambda expression). بالتالي، إذا كان المُعدَّد فارغًا، سيُعيد التابع القيمة المنطقية true
دومًا بغض النظر عن شرط الدالة المجردة، كالمثال التالي:
var numbers = new int[0]; var allNumbersArePositive = numbers.All(n => n > 0); Console.WriteLine(allNumbersArePositive); //True
التابع Sum
var numbers = new[] {1,2,3,4}; var sumOfAllNumbers = numbers.Sum(); Console.WriteLine(sumOfAllNumbers); //10 var cities = new[] { new {Population = 1000}, new {Population = 2500}, new {Population = 4000} }; var totalPopulation = cities.Sum(c => c.Population); Console.WriteLine(totalPopulation); //7500
التابع SequenceEqual
var numbers = new[] {1,2,3,4,5}; var sameNumbers = new[] {1,2,3,4,5}; var sameNumbersInDifferentOrder = new[] {5,1,4,2,3}; var equalIfSameOrder = numbers.SequenceEqual(sameNumbers); Console.WriteLine(equalIfSameOrder); //True var equalIfDifferentOrder = numbers.SequenceEqual(sameNumbersInDifferentOrder); Console.WriteLine(equalIfDifferentOrder); //False
التابع Min
var numbers = new[] {1,2,3,4}; var minNumber = numbers.Min(); Console.WriteLine(minNumber); //1 var cities = new[] { new {Population = 1000}, new {Population = 2500}, new {Population = 4000} }; var minPopulation = cities.Min(c => c.Population); Console.WriteLine(minPopulation); //1000
التابع Distinct
var numbers = new[] {1, 1, 2, 2, 3, 3, 4, 4, 5, 5}; var distinctNumbers = numbers.Distinct(); Console.WriteLine(string.Join(",", distinctNumbers)); //1,2,3,4,5
التابع Count
IEnumerable<int> numbers = new[] {1,2,3,4,5,6,7,8,9,10}; var numbersCount = numbers.Count(); Console.WriteLine(numbersCount); //10 var evenNumbersCount = numbers.Count(n => (n & 1) == 0); Console.WriteLine(evenNumbersCount); //5
التابع Cast
يَختلف التابع Cast
عن بقية توابع النوع Enumerable
من جِهة كَوْنه تابعًا مُوسِّعًا للواجهة IEnumerable
لا نظيرتها المُعمَّمة IEnumerable<T>
. بالتالي، قد يُستَخَدم لتحويل نُسخ من الواجهة غير المُعمَّمة لآخرى مُعمَّمة.
فمثلًا، لا يجتاز المثال التالي عملية التَصرِّيف (compilation)؛ نظرًا لأن التابع First
-كغالبية توابع النوع Enumerable
- هو تابِع موسِّع للواجهة IEnumerable<T>
، ولمّا كان النوع ArrayList
لا يُنَفِذ تلك الوَاجِهة وإنما يُنَفِذ الواجهة غيْر المُعمَّمة IEnumerable
، تَفشل عملية التَصرِّيف.
var numbers = new ArrayList() {1,2,3,4,5}; Console.WriteLine(numbers.First());
في المُقابِل، يَعمل المثال التالي بشكل سليم:
var numbers = new ArrayList() {1,2,3,4,5}; Console.WriteLine(numbers.Cast<int>().First()); //1
لا يُجرِي التابع Cast
عملية التحويل بين اﻷنواع (Casting). فمثلًا، في حين تجتاز الشيفرة التالية عملية التَصرِّيف (compilation)، يُبلَّغ عن اعتراض من النوع InvalidCastException
خلال زمن التشغيل (runtime).
var numbers = new int[] {1,2,3,4,5}; decimal[] numbersAsDecimal = numbers.Cast<decimal>().ToArray();
إذا أردت إجراء عملية التحويل بين الأنواع (casting) على التَجميعَات بطريقة صحيحة، نفذ التالي:
var numbers= new int[] {1,2,3,4,5}; decimal[] numbersAsDecimal = numbers.Select(n => (decimal)n).ToArray();
التابع Range
يَستقبِل التابع Range
مُعامِلين، هما قيمة أول رقم بالمُُعدَّد، ورقم يُعبِر عن عدد العناصر بالمُعَّدد النَاتِج -لا قيمة آخر رقم.
// prints 1,2,3,4,5,6,7,8,9,10 Console.WriteLine(string.Join(",", Enumerable.Range(1, 10))); // prints 10,11,12,13,14 Console.WriteLine(string.Join(",", Enumerable.Range(10, 5)));
التابع ThenBy
لا يُستخَدَم التابع ThenBy
إلا بعد استدعاء التابع OrderBy
، مما يَسمَح بترتيب عناصر المُُعدَّد وفقًا لأكثر من معيار.
var persons { new {Id = 1, Name = "Foo", Order = 1}, new {Id = 1, Name = "FooTwo", Order = 2}, new {Id = 2, Name = "Bar", Order = 2}, new {Id = 2, Name = "BarTwo", Order = 1}, new {Id = 3, Name = "Fizz", Order = 2}, new {Id = 3, Name = "FizzTwo", Order = 1}, }; var personsSortedByName = persons.OrderBy(p => p.Id).ThenBy(p => p.Order); Console.WriteLine(string.Join(",", personsSortedByName.Select(p => p.Name))); //This will display : //Foo,FooTwo,BarTwo,Bar,FizzTwo,Fizz
التابع Repeat
يُعيد التابع Enumerable.Repeat
مُتتالية من عدة عناصر تَحمِل جميعها نفس القيمة. يُنتج المثال التالي مُتتالية مكونة من أربع عناصر، يَحوي كلَا منها السلسلة النصية “Hello”.
var repeats = Enumerable.Repeat("Hello", 4); foreach (var item in repeats) { Console.WriteLine(item); } /* output: Hello Hello Hello Hello */
التابع Empty
يُنشِئ التابع Empty
مُعدَّدًا فارغًا من الواجهة IEnumerable<T>
. يَستقبِل التابع نوع المُعدَّد كمُعامِل نوع (type parameter). فمثلًا، يُنشِئ المثال التالي مُُعدَّد من النوع int
:
IEnumerable<int> emptyList = Enumerable.Empty<int>();
يُخزن التابع Empty
كل نوع تم انشائه من المتُعدَّدات الفارغة IEnumerable<T>
تخزينًا مؤقتًا (caching) ويُعيِد استخدامه عند الحاجة لتنشئة نسخة جديدة من نفس النوع، وبالتالي:
Enumerable.Empty<decimal>() == Enumerable.Empty<decimal>(); // This is True Enumerable.Empty<int>() == Enumerable.Empty<decimal>(); // This is False
التابع Select (ربط map)
var persons { new {Id = 1, Name = "Foo"}, new {Id = 2, Name = "Bar"}, new {Id = 3, Name = "Fizz"}, new {Id = 4, Name = "Buzz"}, }; var names = persons.Select(p => p.Name); Console.WriteLine(string.Join(",", names.ToArray())); //Foo,Bar,Fizz,Buzz
تُطلِق لغات البرمجة الوَظِيفية (functional languages) اسم الرَبْط (map) على هذا النوع من الدوال.
التابع OrderBy
var persons { new {Id = 1, Name = "Foo"}, new {Id = 2, Name = "Bar"}, new {Id = 3, Name = "Fizz"}, new {Id = 4, Name = "Buzz"}, }; var personsSortedByName = persons.OrderBy(p => p.Name); Console.WriteLine(string.Join(",", personsSortedByName.Select(p => p.Id).ToArray())); //2,4,3,1
التابع OrderByDescending
var persons { new {Id = 1, Name = "Foo"}, new {Id = 2, Name = "Bar"}, new {Id = 3, Name = "Fizz"}, new {Id = 4, Name = "Buzz"}, }; var personsSortedByNameDescending = persons.OrderByDescending(p => p.Name); Console.WriteLine(string.Join(",", personsSortedByNameDescending.Select(p => p.Id).ToArray())); //1,3,4,2
التابع Contains
var numbers = new[] {1,2,3,4,5}; Console.WriteLine(numbers.Contains(3)); //True Console.WriteLine(numbers.Contains(34)); //False
التابع First (بحث وجلب find)
var numbers = new[] {1,2,3,4,5}; var firstNumber = numbers.First(); Console.WriteLine(firstNumber); //1 var firstEvenNumber = numbers.First(n => (n & 1) == 0); Console.WriteLine(firstEvenNumber); //2
عند عدم تواجد أي عناصر تُحقِق شرط الدالة المجردة المُمرَّرة للتابع، يُبلَّغ عن اعتراض من النوع InvalidOperationException
مَصحُوبًا بالرسالة التالية "لا تحتوي المتتالية على أية عناصر مُتوافِقة".
var firstNegativeNumber = numbers.First(n => n < 0);
التابع Single
var oneNumber = new[] {5}; var theOnlyNumber = oneNumber.Single(); Console.WriteLine(theOnlyNumber); //5 var numbers = new[] {1,2,3,4,5}; var theOnlyNumberSmallerThanTwo = numbers.Single(n => n < 2); Console.WriteLine(theOnlyNumberSmallerThanTwo); //1
عند تواجد أكثر من عنصر يُحقِق شرط الدالة المجردة أو عند عدم وجوده نهائيًا كاﻷمثلة التالية، يُبلِّغ التابع Single
عن اعتراض من النوع InvalidOperationException
.
var theOnlyNumberInNumbers = numbers.Single(); var theOnlyNegativeNumber = numbers.Single(n => n < 0);
التابع Last
var numbers = new[] {1,2,3,4,5}; var lastNumber = numbers.Last(); Console.WriteLine(lastNumber); //5 var lastEvenNumber = numbers.Last(n => (n & 1) == 0); Console.WriteLine(lastEvenNumber); //4
في المثال التالي، يُبلَّغ عن اعتراض من النوع InvalidOperationException
لعدَم وجود أيّ عنصر يُحقِق شرط الدالة المُجردَّة:
var lastNegativeNumber = numbers.Last(n => n < 0);
التابع LastOrDefault
var numbers = new[] {1,2,3,4,5}; var lastNumber = numbers.LastOrDefault(); Console.WriteLine(lastNumber); //5 var lastEvenNumber = numbers.LastOrDefault(n => (n & 1) == 0); Console.WriteLine(lastEvenNumber); //4 var lastNegativeNumber = numbers.LastOrDefault(n => n < 0); Console.WriteLine(lastNegativeNumber); //0 var words = new[] { "one", "two", "three", "four", "five" }; var lastWord = words.LastOrDefault(); Console.WriteLine(lastWord); // five var lastLongWord = words.LastOrDefault(w => w.Length > 4); Console.WriteLine(lastLongWord); // three var lastMissingWord = words.LastOrDefault(w => w.Length > 5); Console.WriteLine(lastMissingWord); // null
التابع SingleOrDefault
var oneNumber = new[] {5}; var theOnlyNumber = oneNumber.SingleOrDefault(); Console.WriteLine(theOnlyNumber); //5 var numbers = new[] {1,2,3,4,5}; var theOnlyNumberSmallerThanTwo = numbers.SingleOrDefault(n => n < 2); Console.WriteLine(theOnlyNumberSmallerThanTwo); //1 var theOnlyNegativeNumber = numbers.SingleOrDefault(n => n < 0); Console.WriteLine(theOnlyNegativeNumber); //0
يَختلف عن التابع Single
عند عدم تواجد أي عنصر يُحقِق شرط الدالة المجردة، ففي هذه الحالة، لا يُبلِّغ التابع SingleOrDefault
عن اعتراض، وإنما يُعيد القيمة الافتراضية للنوع الذي يحتويه المُُعدَّد. أما في حالة تواجد أكثر من عنصر، فإنه -مثل التابع Single
- يُبلِّغ عن اعتراض من النوع InvalidOperationException
كالمثال التالي:
var theOnlyNumberInNumbers = numbers.SingleOrDefault();
التابع FirstOrDefault
var numbers = new[] {1,2,3,4,5}; var firstNumber = numbers.FirstOrDefault(); Console.WriteLine(firstNumber); //1 var firstEvenNumber = numbers.FirstOrDefault(n => (n & 1) == 0); Console.WriteLine(firstEvenNumber); //2 var firstNegativeNumber = numbers.FirstOrDefault(n => n < 0); Console.WriteLine(firstNegativeNumber); //0 var words = new[] { "one", "two", "three", "four", "five" }; var firstWord = words.FirstOrDefault(); Console.WriteLine(firstWord); // one var firstLongWord = words.FirstOrDefault(w => w.Length > 3); Console.WriteLine(firstLongWord); // three var firstMissingWord = words.FirstOrDefault(w => w.Length > 5); Console.WriteLine(firstMissingWord); // null
التابع Skip
يقوم التابع Skip
بتَخطِي أول مجموعة من العناصر بالمُعدَّد. عدد تلك العناصر يُحدِّده مُعامل دَخْل يُمرَّر للتابع. عندما يَصِل التابع Skip
إلى أول عنصر بعد تلك المجموعة، يبدأ بإعادة قيم العناصر عند تَعدِّيدها.
var numbers = new[] {1,2,3,4,5}; var allNumbersExceptFirstTwo = numbers.Skip(2); Console.WriteLine(string.Join(",", allNumbersExceptFirstTwo.ToArray())); //3,4,5
التابع Take
يَجِلْب التابع Take
أول مجموعة عناصر من المُعدَّد، ويحدِّد عددها مُعامل دَخْل يُمرَّر للتابع.
var numbers = new[] {1,2,3,4,5}; var threeFirstNumbers = numbers.Take(3); Console.WriteLine(string.Join(",", threeFirstNumbers.ToArray())); //1,2,3
التابع Reverse
var numbers = new[] {1,2,3,4,5}; var reversed = numbers.Reverse(); Console.WriteLine(string.Join(",", reversed.ToArray())); //5,4,3,2,1
التابع OfType
var mixed = new object[] {1,"Foo",2,"Bar",3,"Fizz",4,"Buzz"}; var numbers = mixed.OfType<int>(); Console.WriteLine(string.Join(",", numbers.ToArray())); //1,2,3,4
التابع Max
var numbers = new[] {1,2,3,4}; var maxNumber = numbers.Max(); Console.WriteLine(maxNumber); //4 var cities = new[] { new {Population = 1000}, new {Population = 2500}, new {Population = 4000} }; var maxPopulation = cities.Max(c => c.Population); Console.WriteLine(maxPopulation); //4000
التابع Average
var numbers = new[] {1,2,3,4}; var averageNumber = numbers.Average(); Console.WriteLine(averageNumber); // 2,5
يَحسِب التابع Average
في المثال باﻷعلى قيمة مُتوسِط مُعدَّد من النوع العَدَدي.
var cities = new[] { new {Population = 1000}, new {Population = 2000}, new {Population = 4000} }; var averagePopulation = cities.Average(c => c.Population); Console.WriteLine(averagePopulation); // 2333,33
يَحِسب التابع في المثال بالأعلى قيمة مُتوسِط المُعدَّد بالاعتماد على مُفوِّض (delegated function) يُمرَّر له.
التابع GroupBy
var persons = new[] { new { Name="Fizz", Job="Developer"}, new { Name="Buzz", Job="Developer"}, new { Name="Foo", Job="Astronaut"}, new { Name="Bar", Job="Astronaut"}, }; var groupedByJob = persons.GroupBy(p => p.Job); foreach(var theGroup in groupedByJob) { Console.WriteLine( "{0} are {1}s", string.Join(",", theGroup.Select(g => g.Name).ToArray()), theGroup.Key); } //Fizz,Buzz are Developers //Foo,Bar are Astronauts
في المثال التالي، تُجَمَّع الفواتير وفقًا لقيمة الدولة، ويُنشَّئ لكلًا منها كائن جديد يحتوي على عدد السِجلات، والقيمة المدفوعة الكلية، ومتوسط القيمة المدفوعة:
var a = db.Invoices.GroupBy(i => i.Country) .Select(g => new { Country = g.Key, Count = g.Count(), Total = g.Sum(i => i.Paid), Average = g.Average(i => i.Paid) });
إذا أردنا المدفوعات فقط بدون مجموعات:
var a = db.Invoices.GroupBy(i => 1) .Select(g => new { Count = g.Count(), Total = g.Sum(i => i.Paid), Average = g.Average(i => i.Paid) });
إذا أردنا أكثر من تِعداد:
var a = db.Invoices.GroupBy(g => 1) .Select(g => new { High = g.Count(i => i.Paid >= 1000), Low = g.Count(i => i.Paid < 1000), Sum = g.Sum(i => i.Paid) });
التابع ToDictionary
يُحول التابع ToDictionary
مُعامِل مُعدَّد من الوَاجِهة IEnumerable
-يُدعى المصدر source
- إلى قاموس (Dictionary)، بالاستعانة بمُعامِل دالة مُجردة تُمرَّر للتابع -تُدعى keySelector
- لتحديد مفاتيح القاموس (keys).
يُبلِّغ التابع عن اعتراض من النوع ArgumentException
إذا كانت الدالة المُجردَّة keySelector
غير مُتباينة (الدالة المُتباينة injective هي دالة تعيد قيمة فريدة (unique) لكل عنصر بمُعامل التَجمِيعَة source
).
تتوفر توابع التحميل الزائد (overloads) من التابع ToDictionary
، والتي تَستقبِل دوال مجردة لتخصيص مفاتيح (keys) وقيم (value) القاموس.
var persons = new[] { new { Name="Fizz", Id=1}, new { Name="Buzz", Id=2}, new { Name="Foo", Id=3}, new { Name="Bar", Id=4}, };
إذا ما خُصِّصَت دالة اختيار المفتاح فقط، سيُنشَّئ قاموس من النوع Dictionary<TKey,TVal>
. بحيث يكون نوع المفتاح TKey
من نفس نوع القيمة العائدة من الدالة KeySelector
، بينما يكون نوع القيمة TVal
من نفس نوع الكائن اﻷصلي ويحمل قيمته.
var personsById = persons.ToDictionary(p => p.Id); // personsById is a Dictionary<int,object> Console.WriteLine(personsById[1].Name); //Fizz Console.WriteLine(personsById[2].Name); //Buzz
أما إذا خُصّصت دالة اختيار القيمة ValueSelector
أيضًا، فسيُنشَّئ قاموس من النوع Dictionary<TKey,TVal>
. بحيث يكون نوع المفتاح TKey
من نفس نوع القيمة العائدة من الدالة KeySelector
، ويكون نوع القيمة TVal
من نفس نوع القيمة العائدة من الدالة ValueSelector
ويحمل قيمتها الفِعلية تِباعًا.
var namesById = persons.ToDictionary(p => p.Id, p => p.Name); //namesById is a Dictionary<int,string> Console.WriteLine(namesById[3]); //Foo Console.WriteLine(namesById[4]); //Bar
كما ذُكر باﻷعلى، يجب أن تكون المفاتيح -التي تعيدها دالة اختيار المفاتيح- فريدة. في المثال التالي، يُبلَّغ عن اعتراض لمُخالفة هذا الشرط:
var persons = new[] { new { Name="Fizz", Id=1}, new { Name="Buzz", Id=2}, new { Name="Foo", Id=3}, new { Name="Bar", Id=4}, new { Name="Oops", Id=4} }; var willThrowException = persons.ToDictionary(p => p.Id)
إذا لم يكن بالإمكان تخصيص مفتاح فريد لكل عنصر بالتَجمِيعَة، يُمكِنك استخدام التابع ToLookUp
. ظاهريًا، يعمل التابع ToLookup
بصورة مشابهة للتابع ToDictionary
، بِخلاف إمكانية ربطه لكل مفتاح بتَجمِيعَة من القيم التي تَحمِل نفس المفتاح.
التابع Union
var numbers1to5 = new[] {1,2,3,4,5}; var numbers4to8 = new[] {4,5,6,7,8}; var numbers1to8 = numbers1to5.Union(numbers4to8); Console.WriteLine(string.Join(",", numbers1to8)); //1,2,3,4,5,6,7,8
يَحذِف التابع Union
العناصر المُكرّرة. اِستخدِم التابع Concat
إذا لم يكن ذلك ما ترغب به.
التابع ToArray
var numbers = new[] {1,2,3,4,5,6,7,8,9,10}; var someNumbers = numbers.Where(n => n < 6); Console.WriteLine(someNumbers.GetType().Name); //WhereArrayIterator`1 var someNumbersArray = someNumbers.ToArray(); Console.WriteLine(someNumbersArray.GetType().Name); //Int32[]
التابع ToList
var numbers = new[] {1,2,3,4,5,6,7,8,9,10}; var someNumbers = numbers.Where(n => n < 6); Console.WriteLine(someNumbers.GetType().Name); //WhereArrayIterator`1 var someNumbersList = someNumbers.ToList(); Console.WriteLine( someNumbersList.GetType().Name + " - " + someNumbersList.GetType().GetGenericArguments()[0].Name); //List`1 - Int32
التابع List.ForEach
التابع ForEach
مُعرف بالصنف List<T>
، وليس بأيّ من الوَاجِهتين IQueryable<T>
أو IEnumerable<T>
. ومِنْ ثَمَّ إذا أردت استدعاء هذا التابع من خلال كائن من احدى هاتين الوَاجِهتين، فلديك خيارين:
-
الخيار اﻷول: استدعاء التابع
ToList
أولًا
عند استدعاء التابع ToList
، سيحدث شيئًا من اثنين اعتمادًا على وَاجِهة الكائن. إذا كان الكائن من الوَاجِهة IEnumerable<T>
، ستُجرَى عملية تِعِداد (enumeration) للمُعدَّد، مما يؤدي إلى نَسخ النتيجة إلى قائمة جديدة (List). أما إذا كان الكائن من الوَاجِهة IQueryable<T>
، فستُجرَى عملية اتصال بقاعدة البيانات لتنفيذ عبارة استعلام (query) مُعيّنة. وأخيرًا، سيُستدعَى التابع ForEach
لكل عنصر بالقائمة. مثلًا:
IEnumerable<Customer> customers = new List<Customer>(); customers.ToList().ForEach(c => c.SendEmail());
تُعاني هذه الطريقة مِن استهلاك غير ضروري للذاكرة بسبب الاضطرار لإنشاء مؤقت لقائمة (List).
- الخيار الثاني: استخدام التابع المُوسِّع (Extension Method)
أضِف التابع المُوسِّع التالي للواجهة IEnumerable<T>
كالتالي:
public static void ForEach<T>(this IEnumerable<T> enumeration, Action<T> action) { foreach(T item in enumeration) { action(item); } }
ثم اِستَدعه مباشرة من خلال الوَاجِهة كالتالي:
IEnumerable<Customer> customers = new List<Customer>(); customers.ForEach(c => c.SendEmail());
تنبيه: صُممت توابع الاستعلامات التكميلية اللغوية LINQ لتكون نَقيَّة (pure methods)، مما يَعنيّ عدم تَسَبّبهم بأيّ تأثيرات جانبية (side effects). يَنحرِف التابع ForEach
عن بقية التوابع من هذه الناحية لأن هدفه اﻷوحد هو إحِداث تأثير جانبي. بدلًا من ذلك، قد تُنفِذ التِكرار (loop) باستخدام الكلمة المفتاحية foreach
، وهذا، في الواقع، ما تَفعله الدالة المُوسِّعة باﻷعلى داخليًا.
في حالة كنت تُريد إجراء التكرار على مُتغير من النوع List، يُمكنك استدعاء التابع ForEach
مُباشِرة دون الحاجة إلى أي توابع مُوسِّعة، كالتالي:
public class Customer { public void SendEmail() { // Sending email code here } } List<Customer> customers = new List<Customer>(); customers.Add(new Customer()); customers.Add(new Customer()); customers.ForEach(c => c.SendEmail());
التابع ElementAt
يُبلِّغ التابع ElementAt
عن اعتراض من النوع ArgumentOutOfRangeException
عند تمرِّير فِهرَس (index) خارج مدى (Range) المُتعدَّد.
var names = new[] {"Foo","Bar","Fizz","Buzz"}; var thirdName = names.ElementAt(2); Console.WriteLine(thirdName); //Fizz
سيُبلَّغ عن اعتراض ArgumentOutOfRangeException
في المثال التالي:
var names = new[] {"Foo","Bar","Fizz","Buzz"}; var minusOnethName = names.ElementAt(-1); var fifthName = names.ElementAt(4);
التابع ElementAtOrDefault
var names = new[] {"Foo","Bar","Fizz","Buzz"}; var thirdName = names.ElementAtOrDefault(2); Console.WriteLine(thirdName); //Fizz var minusOnethName = names.ElementAtOrDefault(-1); Console.WriteLine(minusOnethName); //null var fifthName = names.ElementAtOrDefault(4); Console.WriteLine(fifthName); //null
التابع SkipWhile
var numbers = new[] {2,4,6,8,1,3,5,7}; var oddNumbers = numbers.SkipWhile(n => (n & 1) == 0); Console.WriteLine(string.Join(",", oddNumbers.ToArray())); //1,3,5,7
التابع TakeWhile
var numbers = new[] {2,4,6,1,3,5,7,8}; var evenNumbers = numbers.TakeWhile(n => (n & 1) == 0); Console.WriteLine(string.Join(",", evenNumbers.ToArray())); //2,4,6
التابع DefaultIfEmpty
var numbers = new[] {2,4,6,8,1,3,5,7}; var numbersOrDefault = numbers.DefaultIfEmpty(); Console.WriteLine(numbers.SequenceEqual(numbersOrDefault)); //True var noNumbers = new int[0]; var noNumbersOrDefault = noNumbers.DefaultIfEmpty(); Console.WriteLine(noNumbersOrDefault.Count()); //1 Console.WriteLine(noNumbersOrDefault.Single()); //0 var noNumbersOrExplicitDefault = noNumbers.DefaultIfEmpty(34); Console.WriteLine(noNumbersOrExplicitDefault.Count()); //1 Console.WriteLine(noNumbersOrExplicitDefault.Single()); //34
التابع Join
class Developer { public int Id { get; set; } public string Name { get; set; } } class Project { public int DeveloperId { get; set; } public string Name { get; set; } } var developers = new[] { new Developer { Id = 1, Name = "Foobuzz" }, new Developer { Id = 2, Name = "Barfizz" } }; var projects = new[] { new Project { DeveloperId = 1, Name = "Hello World 3D" }, new Project { DeveloperId = 1, Name = "Super Fizzbuzz Maker" }, new Project { DeveloperId = 2, Name = "Citizen Kane - The action game" }, new Project { DeveloperId = 2, Name = "Pro Pong 2016" } }; var denormalized = developers.Join( inner: projects, outerKeySelector: dev => dev.Id, innerKeySelector: proj => proj.DeveloperId, resultSelector: (dev, proj) => new { ProjectName = proj.Name, DeveloperName = dev.Name}); foreach(var item in denormalized) { Console.WriteLine("{0} by {1}", item.ProjectName, item.DeveloperName); } //Hello World 3D by Foobuzz //Super Fizzbuzz Maker by Foobuzz //Citizen Kane - The action game by Barfizz //Pro Pong 2016 by Barfizz
مثال آخر عن التابع Join مع تطبيق الضم الخارجي اليساري (left outer join):
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } public static void Main(string[] args) { var magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; var terry = new Person { FirstName = "Terry", LastName = "Adams" }; var barley = new Pet { Name = "Barley", Owner = terry }; var people = new[] { magnus, terry }; var pets = new[] { barley }; var query = from person in people join pet in pets on person equals pet.Owner into gj from subpet in gj.DefaultIfEmpty() select new { person.FirstName, PetName = subpet?.Name ?? "-" // Use - if he has no pet }; foreach (var p in query) Console.WriteLine($"{p.FirstName}: {p.PetName}"); }
ترجمة -وبتصرف- للفصل LINQ والفصل ForEach من كتاب .NET Framework Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.