بررسی چند اپراتور مهم در LINQ

28 اردیبهشت 1398
درسنامه درس 10 از سری آموزش LINQ
LINQ-operators

با سلام خدمت کاربران عزیز در این جلسه قصد داریم چند اپراتور باقی مانده در مبحث LINQ را بررسی کنیم تا پیش نیاز لازم را برای شروع مبحث LINQ to SQL داشته باشیم و از جلسه ی بعدی به توضیح و بررسی آن بپردازیم.

متد Aggregate

کاربرد این متد زمانی است که می خواهیم کل عناصر یک مجموعه بر اساس الگوی خاصی رفتار کنند، این رفتار با شروع از عنصر اول و دوم و اعمال نتیجه به عنصر سوم و الی آخر انجام می شود. احتمالا توضیح آن کمی گیج کننده بود اجازه دهید با بررسی یک مثال کاربرد آن را شرح دهیم.

مثال:

using System;
using System.Linq;
namespace LINQExamples
{
    class Program
    {
        static void Main(string[] args)
        {

            int[] Num = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            Console.WriteLine("Find the Product of the elements:");
            double Average = Num.Aggregate((a, b) => a * b);
            Console.WriteLine("The Product is {0}", Average); 
            string[] charlist = { "a", "b", "c", "d" };
            var concta = charlist.Aggregate((a, b) => a + ',' + b);
            Console.WriteLine("Concatenated String: {0}", concta); 
            Console.ReadLine();
        }
    }
}

در ابتدای برنامه یک آرایه int در اختیار داریم که اعضای آن اعداد 1 تا 9 هستند. هدف ما گرفتن فاکتوریل از اعضای آرایه است.

فرمول فاکتوریل به این صورت است که عدد اول و دوم در هم ضرب می شوند و حاصل آن با عدد سوم ضرب و به همین صورت تا پایان ادامه پیدا می کند.

این الگو به راحتی در متد Aggregate قابل پیاده سازی است برای این منظور کد a, b) => a * b) درون متد می نویسیم. منظور از a عدد اول و b عدد دوم است. در مرحله ی اول a=1 و b=2 است که حاصل ضرب آن ها عدد 2 خواهد بود حالا a=2 (حاصل مرحله ی اول) و b=3 است که حاصل ضرب آن ها 6 خواهد بود به همین ترتیب در مرحله ی بعد a=6 و b=4 می شود تا به پایان برسد.

در مرحله ی بعد یک آرایه ی string تعریف شده است که ما قصد داریم اعضای آن همراه با ',' یا هر چیز دیگری از هم جدا کنیم برای تکرار این الگو برای تمامی اعضا کدa, b) => a + "," + b) را در متد می نویسیم.

برای پیاده سازی کد به روش Query باید تغییرات زیر را انجام دهید:

 double Average = (from x in Num
                   select x).Aggregate((a, b) => a * b);
 string concta = (from x in charlist
               select x).Aggregate((a, b) => a + "," + b);
خروجی مثال Aggregate
خروجی مثال Aggregate

همان طور که در خروجی مشخص است مقدار 9! محاسبه شده است و اعضای آرایه ی string با ',' جدا شده اند.

حالا تغییرات زیر را در کد بالا اعمال کنید و خروجی را مشاهده کنید:

double Average = Num.Aggregate((a,b)=>(a+1)+b);
string concta = charlist.Aggregate((a, b) => a + "->\t" + b);

در مرحله ی اول a=1 و b=2 خواهد بود. همان طور که در الگو مشخص شده ابتدا باید یک واحد به a اضافه و سپس با b جمع شود که حاصل برابر 4 خواهد شد در مرحله ی بعد a=4 و b=3 می شود. دوباره باید به مقدار a یک واحد اضافه و سپس با b جمع شود تا حاصل برابر 8 شود این عملیات تا رسیدن به آخرین عضو آرایه به همین ترتیب ادامه خواهد داشت تا در آخر عدد 53 حاصل شود.

در مورد آرایه ی string هدف ما ابتدا چاپ -> و سپس ایجاد کارکتر کنترلی "t\" است. برنامه را اجرا و خروجی را مشاده کنید.

خروجی مثال بعد از تغییرات
خروجی مثال بعد از تغییرات

متد GroupBy

از این متد برای گروه بندی آیتم ها در یک مجموعه یا لیست بر اساس یک کلید خاص و یک فیلد استفاده می شود. این متد از نظر کاربرد تا حد زیادی به GroupBy در SQL شباهت دارد.

به مثال زیر توجه کنید:

using System;
using System.Collections.Generic;
using System.Linq;
namespace Linqtutorials
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Student> objStudent = new List<Student>()
            {
                new Student() { Name = "Suresh Dasari", Gender = "Male",Location="Chennai" },
                new Student() { Name = "Rohini Alavala", Gender = "Female", Location="Chennai" },
                new Student() { Name = "Praveen Alavala", Gender = "Male",Location="Bangalore" },
                new Student() { Name = "Sateesh Alavala", Gender = "Male", Location ="Vizag"},
                new Student() { Name = "Madhav Sai", Gender = "Male", Location="Nagpur"}
             };
            var student = objStudent.GroupBy(x => x.Location);
            foreach (var sitem in student)
            {
                Console.WriteLine(sitem.Key+" "+ sitem.Count());
                Console.WriteLine();
                foreach (var stud in sitem)
                {
                    Console.WriteLine(stud.Name + "\t" + stud.Location);
                }
                Console.WriteLine();
            }
            Console.ReadLine();
        }
    }
    class Student
    {
        public string Name { get; set; }
        public string Gender { get; set; }
        public string Location { get; set; }
    }
}

هدف ما در این مثال مرتب سازی اعضای یک مجموعه بر اساس فیلد Location است. توجه داشته باشید که در این متد منظور از Key همان فیلدی است که اطلاعات بر اساس آن طبقه بندی می شود که در این مثال مقدار Key فیلد Location است.

در foreach اول هدف ما چاپ مقادیر فیلد Location و تعداد آن است همان طور که مشاهده می کنید برای دستیابی به مقادیر فیلد از کلمه ی Key استفاده شده است.

در foreach دومی که در داخل اولی قرار دارد، قصد داریم به فیلد های کلاس Student دست پیدا کنیم به همین منظور بر روی شئ پیمایش کننده حلقه ی اول یک پیمایش دیگر انجام می دهیم اگر موس را بر روی stud قرار دهید می بینید که شئ از  کلاس Student است. بنابراین به تمامی فیلد های آن دسترسی دارد و می توان با دستور مناسب آن را چاپ نمود.

روش Query:

 var student = from x in objStudent.GroupBy(x => x.Location)
 select x;

یا

var student = from x in objStudent
              group x by x.Location;
خروجی مثال اول GroupBy
خروجی مثال اول GroupBy

احتمالا متوجه شدید که این متد شباهت بسیار زیادی به متد ToLookup دارد حالا اجازه دهید همین مثال را با متد ToLookup تغییر دهیم و آن را بررسی کنیم.

تغییرات زیر را بر روی مثال قبلی انجام دهید:

var student = objStudent.ToLookup(x => x.Location);
            foreach (var sitem in student)
            {
                Console.WriteLine(sitem.Key+" "+ sitem.Count());
                Console.WriteLine();
                foreach (var stud in student[sitem.Key])
                {
                    Console.WriteLine(stud.Name + "\t" + stud.Location);
                }
                Console.WriteLine();
            }

فقط کافی است جای GroupBy را با ToLookup عوض کنید در ضمن در کد بالا در حلقه ی دوم می توانید به جای [student[sitem.Key از sitem (پیمایشگر حلقه ی اول) استفاده کنید.

اگر کد را اجرا کنید می بینید که خروجی دقیقا مانند قبل است فقط روش پیاده سازی آن متفاوت است.

متد SequenceEqual

یک متد مقایسه گر است که تست تساوی را بر روی دو دنباله از اطلاعات انجام می دهد. خروجی این متد در صورتی که تمامی عناصر یک مجموعه دو به دو یکسان باشند true و در غیر این صورت false است.

به مثال زیر توجه کنید:

using System;
using System.Collections.Generic;
using System.Linq;
namespace Linqtutorials
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] arr1 = { "welcome", "to", "tutlane", "com" };   
            string[] arr2 = { "welcome", "TO", "TUTLANE", "com" };
            string[] arr3 = { "welcome", "to", "tutlane" };
            string[] arr4 = { "WELCOME", "TO", "TUTLANE" };
            bool res1 = arr1.SequenceEqual(arr2);
            bool res2 = arr1.SequenceEqual(arr2, StringComparer.OrdinalIgnoreCase);
            bool res3 = arr1.SequenceEqual(arr3);
            bool res4 = arr3.SequenceEqual(arr4, StringComparer.OrdinalIgnoreCase);
            Console.WriteLine("Result1: {0}", res1);
            Console.WriteLine("Result2: {0}", res2);
            Console.WriteLine("Result3: {0}", res3);
            Console.WriteLine("Result4: {0}", res4);
            Console.ReadLine();
        }
    }

}

اعضای موجود در آرایه های arr1 و arr2 کاملا یکسان هستند اما در کوچک و بزرگ بودن حروف متفاوت اند. در دو آرایه ی arr3 و arr4 هم شرایط به همین صورت است. با استفاده از متد SequenceEqual شرایط تساوی را بررسی کردیم. برنامه را اجرا و خروجی را مشاهده کنید می بینید که Result1 و Result3 هر دو false هستند.

Result1 حاصل تست تساوی بین دو آرایه ی arr1 و arr2 است. پس نتیجه می گیریم که متد SequenceEqual به بزرگ یا کوچک بودن حروف حساس است برای این که این حساسیت را از بین ببریم باید از StringComparer.OrdinalIgnoreCase در متد استفاده کنیم. می بینید که Result2 و Result4 هر دو true هستد چون در بررسی تساوی از StringComparer.OrdinalIgnoreCase در متدSequenceEqual استفاده کرده ایم.

توجه: این روش تست تساوی برای کلاس ها با Property های مختلف قابل پیاده سازی نیست. یکی از راه های بررسی تساوی میان دو شئ از یک کلاس پیاده سازی واسط IComparable برای آن کلاس و نوشتن متد CompareTo است.

متد Concat

همان طور که از نام این متد ساده پیداست کاربرد آن در وصل کردن دو مجموعه به یکدیگر است به طوری که اگر در دو مجموعه عنصری تکراری وجود داشته باشد آن عنصر حذف نخواهد شد و در مجموعه ی جدیدی که حاصل اتصال دو مجموعه قبلی است عنصر تکراری به دفعات تکرار در دو مجموعه قبلی تکرار خواهد شد.

این متد تا حدودی شبیه به Union است با این تفاوت که متد Union عناصر تکراری را حذف می کرد و در مجموعه ی جدید عنصری که دارای تکرار بود فقط یک بار در نظر گرفته می شد. برای درک بیشتر به شکل زیر که مفهوم کلی متد Concat را نشان می دهد توجه کنید:

مفهوم کلی متد Concat
مفهوم کلی متد Concat

مثال :

using System;
using System.Collections.Generic;
using System.Linq;
namespace Linqtutorials
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] arr1 = { "a", "b", "c","b", "d" };       
            string[] arr2 = { "c", "d", "e", "f","a" };
            IEnumerable<string> result = arr1.Concat(arr2);
            string res = result.Aggregate((a,b)=>a+","+b);
            foreach (var item in res)
            {
                Console.Write(item);
            }
            Console.ReadLine();
        }
    }
}

در این مثال دو آرایه ی arr1 و arr2 که هر دو دارای عناصر تکراری هستند و با استفاده از متد Concat به هم متصل شده اند. مشاهده می کنید که هیچ حذفی صورت نگرفته و تمامی عناصر تکراری در خروجی چاپ شده اند.

روش Quey:

IEnumerable<string> result = (from x in arr1
select x).Concat(arr2);
خروجی مثال متد Concat
خروجی مثال متد Concat

متد Range

از این متد برای تولید دنباله ای از اعداد در یک محدوده ی خاص استفاده می شود.

الگوی استفاده از آن به صورت زیر است:

IEnumerable<int> obj = Enumerable.Range(int start, int count);

این متد بر روی هیچ مجموعه یا شئ فراخوانی نمی شود و به صورت static استفاده می شود و در کلاس Enumerable قرار دارد. هر دو پارامتر ورودی آن از نوع int هستند که اولی مشخص می کند، تولید دنباله از چه عددی شروع شود و پارامتر دوم تعداد اعضای دنباله را مشخص می کند.

به مثال زیر توجه کنید:

using System;
using System.Collections.Generic;
using System.Linq;
namespace Linqtutorials
{
    class Program
    {
        static void Main(string[] args)
        {

            IEnumerable<int> obj = Enumerable.Range(100, 50);
            foreach (var item in obj)
            {
                Console.WriteLine(item);
            }
            Console.ReadLine();
        }
    }
}

در این مثال شروع شمارش از 100 و تعداد آن ها 10 در نظر گرفته شده بنابراین خروجی از 100 تا 109 خواهد بود که توسط foreach قابل دستیابی است.

متد Repeat

این متد یک عدد را به تعداد مرتبه ی مشخصی تکرار می کند که الگوی استفاده از آن به صورت زیر است:

IEnumerable<int> obj = Enumerable.Repeat(int element, int count);

این متد بر روی هیچ مجموعه یا شئ فراخوانی نمی شود و به صورت static استفاده می شود و در کلاس Enumerable قرار دارد. هر دو پارامتر ورودی آن از نوع int هستند که اولی مشخص می کند، چه عددی باید تکرار شود و پارامتر دوم تعداد تکرار را مشخص می کند.

به مثال زیر توجه کنید:

using System;
using System.Collections.Generic;
using System.Linq;
namespace Linqtutorials
{
    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<int> obj = Enumerable.Repeat(524, 3);
            foreach (var item in obj)
            {
                Console.WriteLine(item);
            }
        
            Console.ReadLine();
        }
    }
}

در این مثال عدد 524 که پارامتر اول متد Repeat است 3 با تکرار می شود.

خب دوستان مقدمه ی آموزش LINQ در C# در این بخش به پایان می رسد. در طول دوره ی مقدماتی سعی شد متدهایی که کاربرد بیشتری دارند با مثال های بیشتری بررسی شوند. متدهایی مثل AsEnumerable و Cast و Range و چند متد دیکر که کمتر در مورد آن ها بحث شد متدهایی هستند که به تنهایی هیچ کاربردی ندارند و استفاده از آن ها بی معنی است و زمانی کاربرد آن ها مفید است که در کنار دیگر متدها استفاده شود.

در قسمت های بعدی آموزش سعی می شود در مثال های پیچیده تر از این متدها استفاده کنیم تا کاربرد دقیق آن ها درک کنید. کلیه ی مثال های بررسی شده در طی این دوره شامل مبحث LINQ to Object بودند بنابراین از قسمت بعدی آموزش مبحث LINQ to SQL را شروع می کنیم.

موفق باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش LINQ توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.