اشتراک‌ها
سایت Built with .NET
Projects built with .net framework, .net core, xamarin, mono, mono game, unity, or godot
سایت Built with .NET
مطالب
حذف سریع رکورد های یک لیست SharePoint با NET. در PowerShell
لطفا توجه فرمایید که جالب‌ترین قسمت این مقاله قابلیت استفاده از کلاس‌های دات نت در دل PowerShell می‌باشد. که در قسمت چهارم کد‌ها مشاهده می‌فرمایید.
حذف تمام رکورد‌های یک لیست شیرپوینت از طریق رابط کاربری SharePoint  مسیر نمیباشد و لازم است برای آن چند خط کد نوشته شود که می‌توانید آن را با console و جالب‌تر از آن با  PowerShell   اجرا کنید.
1- ساده‌ترین روش حذف رکورد‌های شیرپوینت را در روبرو مشاهده می‌فرمایید که به ازای حذف هر رکورد یک رفت و برگشت به پایگاه انجام می‌شود
SPList list = mWeb.GetList(strUrl);
if (list != null)
{
    for (int i = list.ItemCount - 1; i >= 0; i--)
    {
        list.Items[i].Delete();
    }
    list.Update();
}
2- با استفاده از  SPWeb.ProcessBatchData در کد زیر می‌توانیم با سرعت بیشتر و هوشمندانه‌تری، حذف تمام رکورد‌ها را در یک عمل انجام دهیم
public static void DeleteAllItems(string site, string list)
{
    using (SPSite spSite = new SPSite(site))
    {
        using (SPWeb spWeb = spSite.OpenWeb())
        {
            StringBuilder deletebuilder = BatchCommand(spWeb.Lists[list]);
            spSite.RootWeb.ProcessBatchData(deletebuilder.ToString());
        }
    }
}

private static StringBuilder BatchCommand(SPList spList)
{
    StringBuilder deletebuilder= new StringBuilder();
    deletebuilder.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Batch>");
    string command = "<Method><SetList Scope=\"Request\">" + spList.ID +
        "</SetList><SetVar Name=\"ID\">{0}</SetVar><SetVar Name=\"Cmd\">Delete</SetVar></Method>";

    foreach (SPListItem item in spList.Items)
    {
        deletebuilder.Append(string.Format(command, item.ID.ToString()));
    }
    deletebuilder.Append("</Batch>");
    return deletebuilder;
}
3- در قسمت زیر همان روش batch  قبلی را مشاهده می‌فرمایید که با تقسیم کردن batch  ها به تکه‌های 1000 تایی کارایی آن را بالا برده ایم
// We prepare a String.Format with a String.Format, this is why we have a {{0}} 
   string command = String.Format("<Method><SetList Scope=\"Request\">{0}</SetList><SetVar Name=\"ID\">{{0}}</SetVar><SetVar Name=\"Cmd\">Delete</SetVar><SetVar Name=\"owsfileref\">{{1}}</SetVar></Method>", list.ID);
   // We get everything but we limit the result to 100 rows 
   SPQuery q = new SPQuery();
   q.RowLimit = 100;

   // While there's something left 
   while (list.ItemCount > 0)
   {
    // We get the results 
    SPListItemCollection coll = list.GetItems(q);

    StringBuilder sbDelete = new StringBuilder();
    sbDelete.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Batch>");

    Guid[] ids = new Guid[coll.Count];
    for (int i=0;i<coll.Count;i++)
    {
     SPListItem item = coll[i];
     sbDelete.Append(string.Format(command, item.ID.ToString(), item.File.ServerRelativeUrl));
     ids[i] = item.UniqueId;
    }
    sbDelete.Append("</Batch>");

    // We execute it 
    web.ProcessBatchData(sbDelete.ToString());

    //We remove items from recyclebin
    web.RecycleBin.Delete(ids);

    list.Update();
   }
  }
4- در این قسمت به جالب‌ترین و آموزنده‌ترین قسمت این مطلب می‌پردازیم و آن import کردن namespace‌ها و ساختن object‌های دات نت در دل PowerShell هست که می‌توانید به راحتی با مقایسه با کد قسمت قبلی که در console نوشته شده است، آن‌را فرا بگیرید.
برای فهم script پاور شل زیر کافیست به چند نکته ساده زیر دقت کنید 
  • ایجاد متغیر‌ها به سادگی با شروع نوشتن نام متغیر با $ و بدون تعریف نوع آن‌ها انجام می‌شود
  • write-host حکم  write را دارد و واضح است که نوشتن تنهای آن برای ایجاد یک line break می‌باشد. 
  • کامنت کردن با # 
  • عدم وجود semi colon  برای اتمام فرامین
[System.Reflection.Assembly]::Load("Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
[System.Reflection.Assembly]::Load("Microsoft.SharePoint.Portal, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
[System.Reflection.Assembly]::Load("Microsoft.SharePoint.Publishing, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c")
[System.Reflection.Assembly]::Load("System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")

write-host 

# Enter your configuration here
$siteUrl = "http://mysharepointsite.example.com/"
$listName = "Name of my list"
$batchSize = 1000

write-host "Opening web at $siteUrl..."

$site = new-object Microsoft.SharePoint.SPSite($siteUrl)
$web = $site.OpenWeb()
write-host "Web is: $($web.Title)"

$list = $web.Lists[$listName];
write-host "List is: $($list.Title)"

while ($list.ItemCount -gt 0)
{
  write-host "Item count: $($list.ItemCount)"

  $batch = "<?xml version=`"1.0`" encoding=`"UTF-8`"?><Batch>"
  $i = 0

  foreach ($item in $list.Items)
  {
    $i++
    write-host "`rProcessing ID: $($item.ID) ($i of $batchSize)" -nonewline

    $batch += "<Method><SetList Scope=`"Request`">$($list.ID)</SetList><SetVar Name=`"ID`">$($item.ID)</SetVar><SetVar Name=`"Cmd`">Delete</SetVar><SetVar Name=`"owsfileref`">$($item.File.ServerRelativeUrl)</SetVar></Method>"

    if ($i -ge $batchSize) { break }
  }

  $batch += "</Batch>"

  write-host

  write-host "Sending batch..."

  # We execute it 
  $result = $web.ProcessBatchData($batch)

  write-host "Emptying Recycle Bin..."

  # We remove items from recyclebin
  $web.RecycleBin.DeleteAll()

  write-host

  $list.Update()
}

write-host "Done."


نظرات مطالب
آشنایی با Refactoring - قسمت 4
در مورد POCO یا مثالی که زدید:
حین کار با ORMs ، کلاس‌های تعریف شده نمی‌دادنند که آیا ذخیره شده‌اند یا اینکه چگونه ذخیره شده‌اند یا حتی چگونه به بانک اطلاعاتی نگاشت شده‌اند یا نشده‌اند. کل عملیات transperant است (persistence ignorance).  همچنین این نوع کلاس‌ها فقط اهداف display / reference دارند و نه بیشتر. بحث کلاس‌های مثال فوق متفاوت است. ما در مورد ده‌ها و صدها متد موجود در آن‌ها بحث کردیم. این‌ کلاس‌ها هیچکدام در رده تعریف POCO یا Plain old .Net classes قرار نمی‌گیرند.
اشتراک‌ها
ویدیوهای NET Conf 2022.
NET Conf is our annual virtual developer event co-organized by the .NET community and Microsoft and sponsored by the .NET Foundation and ecosystem partners. It is a chance to learn and get inspired for your next software project. You will learn to build for web, mobile, cloud, desktop, games, services, libraries and more for a variety of platforms and devices all with .NET. We have sessions for everyone, no matter if you are just beginning or are a seasoned engineer. We’ll have presentations on .NET 7, C# 11, Azure, Visual Studio, and much more. 
ویدیوهای NET Conf 2022.
مطالب
قابلیت های جدید VisualStudio.NET 2012 - قسمت سوم
اگر شما در زمینه طراحی وب سایت و برنامه‌های کاربردی تحت وب فعالیت دارید حتماً با ابزارهایی مانند Firebug آشنا هستید. معمولاً فرآیند بررسی مشکلات رابط کاربری و موضوعات مشابه آن بصورت زیر بوده است:
 
 

توجه داشته باشید که یک صفحه وب که در مرورگر به نمایش در می‌آید، برآیند فایل‌های جاوا اسکریپت، شیوه نامه ها، User control ها، صفحات ASPX و Master Page‌ها و ... است. افزون بر این آنچه در مرورگر نمایش داده می‌شود با چیزی که ما در محیط طراحی (Visual Studio.NET (Design View  می‌بینیم متفاوت است.
تمام مشکلات و سختی‌های بالا دست به دست هم دادند تا در نسخه جدید نرم افزار Visual Studio.NET شاهد ابزار جادویی با عنوان Page Inspector باشیم.
این ابزار بصورت Real-time امکان نگاشت (mapping) عناصر موجود در نتیجه نهایی برنامه وب را با سورس کد مهیا می‌کند. بدین معنا توسط Page Inspector با حرکت ماوس روی عناصر صفحه در مرورگر، Visual Studio.NET بخشی را که آن عنصر را تولید کرده است (User Control, Master Page, View, و ...) نمایش می‌دهد و شما می‌توانید بلافاصله پس از اعمال تغییرات جدید بر روی سورس کد، نتیجه را روی مرورگر ببنید. البته عکس این موضوع نیز صادق است و شما می‌توانید با حرکت در سورس کد، نتیجه بصری و عناصر HTML ی که در نتیجه تولید می‌شوند را مشاهده کنید. (عناصر متناظر به حالت Select در می‌آیند.)
 

 
از دیگر قابلیت‌های این ابزار نمایش CSS‌های متناظر هر عنصر است. شما می‌توانید هر یک از قوانینی که در Style هر عنصر تعریف کرده اید را فعال و یا غیر فعال کنید. همچنین امکان ویرایش آن‌ها وحود دارد.
همچنین از طریق گزینه File می‌توان لیست تمام فایل‌های سورس صفحه را مشاهده کرد.
 

 
با وجود چنین ابزاری یقیناً داشتن دو مانیتور برای برنامه نویسان و توسعه دهندگان وب کاملاً حیاتی است. چراکه Visual Studio.NET به شما این امکان را می‌دهد تا Page Inspector را در یک مانیتور و نمای سورس را در مانیتور دیگر به نمایش در آورید.
 
نکته:
جهت استفاده از تمام امکانات این ابزار باید دستور زیر را در تگ appsettings فایل web.config اضافه کنید:
 <add key="VisualStudioDesignTime:Enabled" value="true" />
پیشنهاد می‌کنم برای درک بهتر این ابزار و آشنایی با آن ویدئو مربوطه در کانال 9  را از دست ندهید.
 
مطالب
اعمال تغییرات سفارشی به ویژگی AutoMapping در Fluent NHibernate

با کمک Fluent NHibernate می‌توان نگاشت‌ها را به دو صورت خودکار و یا دستی تعریف کرد. در حالت خودکار، روابط بین کلاس‌ها بررسی شده و بدون نیاز به تعریف هیچگونه ویژگی (attribute) خاصی بر روی فیلدها، امکان تشخیص خودکار حالت‌های کلید خارجی، روابط یک به چند، چند به چند و امثال آن وجود دارد. یا اگر نیاز باشد تا اسکریپت تولیدی جهت به روز رسانی بانک اطلاعاتی، طول خاصی را به فیلدی اعمال کند می‌توان از ویژگی‌های NHibernate validator استفاده کرد؛ مانند تعریف طول و نال نبودن یک فیلد که علاوه بر بکارگیری اطلاعات آن در حین تعیین اعتبار ورودی دریافتی، بر روی نحوه‌ی به روز رسانی بانک اطلاعاتی هم تاثیر گذار است:
public class Product
{
public virtual int Id { set; get; }

[Length(120)]
[NotNullNotEmpty]
public virtual string Name { get; set; }

public virtual decimal UnitPrice { get; set; }
}
این نگاشت خودکار یا AutoMapping ،‌ تقریبا در 90 درصد موارد کافی است. فیلد Id را بر اساس یک سری پیش فرض‌هایی که این مورد هم قابل تنظیم است به صورت primary key تعریف می‌کند، طول فیلدها و نحوه‌ی پذیرفتن نال آن‌ها، از ویژگی‌های NHibernate validator گرفته می‌شود و روابط بین کلاس‌ها به صورت خودکار به روابط یک به چند و مانند آن ترجمه می‌شود و نیازی نیست تا کلاسی جداگانه را جهت مشخص سازی صریح این موارد تهیه کرد، یا ویژگی مشخص کننده‌ی دیگری را به فیلدها افزود. اما اگر برای مثال بخواهیم در این کلاس فیلد Name را به صورت Unique معرفی کنیم چه باید کرد؟ به عبارتی تمام آنچه‌ که ویژگی AutoMapping در Fluent NHibernate انجام می‌دهد، بسیار هم عالی؛ اما فقط می‌خواهیم مقادیر یک فیلد منحصربفرد باشد. برای این منظور اینترفیس IAutoMappingOverride تدارک دیده شده است:
public class ProductCustomMappings : IAutoMappingOverride<Product>
{
public void Override(AutoMapping<Product> mapping)
{
mapping.Id(u => u.Id).GeneratedBy.Identity(); //ضروری است
mapping.Map(p => p.Name).Unique();
}
}
در حالت استفاده از اینترفیس IAutoMappingOverride مشخص سازی نحوه‌ی تولید primary key ضروری است و سپس برای نمونه، فیلد Name به صورت منحصربفرد تعریف می‌گردد. در اینجا کل عملیات هنوز از روش AutoMapping پیروی می‌کند اما فیلد Name علاوه بر اعمال ویژگی‌های NHibernate validator، به صورت منحصربفرد نیز معرفی خواهد شد.

مطالب
آشنایی با NHibernate - قسمت اول

NHibernate کتابخانه‌ی تبدیل شده پروژه بسیار محبوب Hibernate جاوا به سی شارپ است و یکی از ORM های بسیار موفق، به شمار می‌رود. در طی تعدادی مقاله قصد آشنایی با این فریم ورک را داریم.

چرا نیاز است تا از یک ORM استفاده شود؟
تهیه قسمت و یا لایه دسترسی به داده‌ها در یک برنامه عموما تا 30 درصد زمان کل تهیه یک محصول را تشکیل می‌دهد. اما باید در نظر داشت که این پروسه‌ی تکراری هیچ کار خارق العاده‌ای نبوده و ارزش افزوده‌ی خاصی را به یک برنامه اضافه نمی‌کند. تقریبا تمام برنامه‌های تجاری نیاز به لایه دسترسی به داده‌ها را دارند. پس چرا ما باید به ازای هر پروژه، این کار تکراری و کسل کننده را بارها و بارها تکرار کنیم؟
هدف NHibernate ، کاستن این بار از روی شانه‌های یک برنامه نویس است. با کمک این کتابخانه، دیگر رویه ذخیره شده‌ای را نخواهید نوشت. دیگر هیچگاه با ADO.Net سر و کار نخواهید داشت. به این صورت می‌توان عمده وقت خود را صرف قسمت‌های اصلی و طراحی برنامه کرد تا کد نویسی یک لایه تکراری. همچنین عده‌ای از بزرگان اینگونه ابزارها اعتقاد دارند که برنامه نویس‌هایی که لایه دسترسی به داده‌ها را خود طراحی می‌کنند، مشغول کلاهبرداری از مشتری‌های خود هستند! (صرف زمان بیشتر برای تهیه یک محصول و همچنین وجود باگ‌های احتمالی در لایه دسترسی به داده‌های طراحی شده توسط یک برنامه نویس نه چندان حرفه‌ای)
برای مشاهده سایر مزایای استفاده از یک ORM لطفا به مقاله "5 دلیل برای استفاده از یک ابزار ORM" مراجعه نمائید.

در ادامه برای معرفی این کتابخانه یک سیستم ثبت سفارشات را با هم مرور خواهیم کرد.

بررسی مدل سیستم ثبت سفارشات

در این مدل ساده‌ی ما، مشتری‌ها (customers) امکان ثبت سفارشات (orders) را دارند. سفارشات توسط یک کارمند (employee) که مسؤول ثبت آن‌ها است به سیستم وارد می‌شود. هر سفارش می‌تواند شامل یک یا چند (one-to-many) آیتم (order items) باشد و هر آیتم معرف یک محصول (product) است که قرار است توسط یک مشتری (customer) خریداری شود. کلاس دیاگرام این مدل به صورت زیر می‌تواند باشد.


نگاشت مدل

زمانیکه مدل سیستم مشخص شد، اکنون نیاز است تا حالات (داده‌ها) آن‌را در مکانی ذخیره کنیم. عموما اینکار با کمک سیستم‌های مدیریت پایگاه‌های داده مانند SQL Server، Oracle، IBM DB2 ، MySql و امثال آن‌ها صورت می‌گیرد. زمانیکه از NHibernate استفاده کنید اهمیتی ندارد که برنامه شما قرار است با چه نوع دیتابیسی کار کند؛ زیرا این کتابخانه اکثر دیتابیس‌های شناخته شده موجود را پشتیبانی می‌کند و برنامه از این لحاظ مستقل از نوع دیتابیس عمل خواهد کرد و اگر نیاز بود روزی بجای اس کیوال سرور از مای اس کیوال استفاده شود، تنها کافی است تنظیمات ابتدایی NHibernate را تغییر دهید (بجای بازنویسی کل برنامه).
اگر برای ذخیره سازی داده‌ها و حالات سیستم از دیتابیس استفاده کنیم، نیاز است تا اشیاء مدل خود را به جداول دیتابیس نگاشت نمائیم. این نگاشت عموما یک به یک نیست (لزومی ندارد که حتما یک شیء به یک جدول نگاشت شود). در گذشته‌ی نچندان دور کتابخانه‌ی NHibernate ، این نگاشت عموما توسط فایل‌های XML ایی به نام hbm صورت می‌گرفت. این روش هنوز هم پشتیبانی شده و توسط بسیاری از برنامه نویس‌ها بکار گرفته می‌شود. روش دیگری که برای تعریف این نگاشت مرسوم است، مزین سازی اشیاء و خواص آن‌ها با یک سری از ویژگی‌ها می‌باشد که فریم ورک برتر این عملیات Castle Active Record نام دارد.
اخیرا کتابخانه‌ی دیگری برای انجام این نگاشت تهیه شده به نام Fluent NHibernate که بسیار مورد توجه علاقمندان به این فریم ورک واقع گردیده است. با کمک کتابخانه‌ی Fluent NHibernate عملیات نگاشت اشیاء به جداول، بجای استفاده از فایل‌های XML ، توسط کدهای برنامه صورت خواهند گرفت. این مورد مزایای بسیاری را همانند استفاده از یک زبان برنامه نویسی کامل برای تعریف نگاشت‌ها، بررسی خودکار نوع‌های داد‌ه‌ای و حتی امکان تعریف منطقی خاص برای قسمت نگاشت برنامه، به همراه خواهد داشت.

آماده سازی سیستم برای استفاده از NHibernate

در ادامه بجای دریافت پروژه سورس باز NHibernate از سایت سورس فورج، پروژه سورس باز Fluent NHibernate را از سایت گوگل کد دریافت خواهیم کرد که بر فراز کتابخانه‌ی NHibernate بنا شده است و آن‌را کاملا پوشش می‌دهد. سورس این کتابخانه را با checkout مسیر زیر توسط TortoiseSVN می‌توان دریافت کرد.





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



همچنین جهت سهولت کار، آخرین نگارش موجود در زمان نگارش این مقاله را از این آدرس نیز می‌توانید دریافت نمائید.

پس از دریافت پروژه، باز کردن فایل solution آن در VS‌ و سپس build کل مجموعه، اگر به پوشه‌های آن مراجعه نمائید، فایل‌های زیر قابل مشاهده هستند:

Nhibernate.dll : اسمبلی فریم ورک NHibernate است.
NHibernate.Linq.dll : اسمبلی پروایدر LINQ to NHibernate می‌باشد.
FluentNHibernate.dll : اسمبلی فریم ورک Fluent NHibernate است.
Iesi.Collections.dll : یک سری مجموعه‌های ویژه مورد استفاده NHibernate را ارائه می‌دهد.
Log4net.dll : فریم ورک لاگ کردن اطلاعات NHibernate می‌باشد. (این فریم ورک نیز جهت عملیات logging بسیار معروف و محبوب است)
Castle.Core.dll : کتابخانه پایه Castle.DynamicProxy2.dll است.
Castle.DynamicProxy2.dll : جهت اعمال lazy loading در فریم ورک NHibernate بکار می‌رود.
System.Data.SQLite.dll : پروایدر دیتابیس SQLite است.
Nunit.framework.dll : نیز یکی از فریم ورک‌های بسیار محبوب آزمون واحد در دات نت فریم ورک است.

برای سادگی مراجعات بعدی، این فایل‌ها را یافته و در پوشه‌ای به نام lib کپی نمائید.

برپایی یک پروژه جدید

پس از دریافت Fluent NHibernate ، یک پروژه Class Library جدید را در VS.Net آغاز کنید (برای مثال به نام NHSample1 ). سپس یک پروژه دیگر را نیز از نوع Class Library به نام UnitTests به این solution ایجاد شده جهت انجام آزمون‌های واحد برنامه اضافه نمائید.
اکنون به پروژه NHSample1 ، ارجاع هایی را به فایل‌های FluentNHibernate.dll و سپس NHibernate.dll در که پوشه lib ایی که در قسمت قبل ساختیم، قرار دارند، اضافه نمائید.



در ادامه یک پوشه جدید به پروژه NHSample1 به نام Domain اضافه کنید. سپس به این پوشه، کلاس Customer را اضافه نمائید:

namespace NHSample1.Domain
{
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string PostalCode { get; set; }
public string City { get; set; }
public string CountryCode { get; set; }
}
}
اکنون نوبت تعریف نگاشت این شیء است. این کلاس باید از کلاس پایه ClassMap مشتق شود. سپس نگاشت‌ها در سازنده‌ی این کلاس باید تعریف گردند.

using FluentNHibernate.Mapping;

namespace NHSample1.Domain
{
class CustomerMapping : ClassMap<Customer>
{
}
}
همانطور که ملاحظه می‌کنید، نوع این کلاس Generic ، همان کلاسی است که قصد داریم نگاشت مرتبط با آن را تهیه نمائیم. در ادامه تعریف کامل این کلاس نگاشت را در نظر بگیرید:

using FluentNHibernate.Mapping;

namespace NHSample1.Domain
{
class CustomerMapping : ClassMap<Customer>
{
public CustomerMapping()
{
Not.LazyLoad();
Id(c => c.Id).GeneratedBy.HiLo("1000");
Map(c => c.FirstName).Not.Nullable().Length(50);
Map(c => c.LastName).Not.Nullable().Length(50);
Map(c => c.AddressLine1).Not.Nullable().Length(50);
Map(c => c.AddressLine2).Length(50);
Map(c => c.PostalCode).Not.Nullable().Length(10);
Map(c => c.City).Not.Nullable().Length(50);
Map(c => c.CountryCode).Not.Nullable().Length(2);
}
}
}
به صورت پیش فرض نگاشت‌های Fluent NHibernate از نوع lazy load هستند که در اینجا عکس آن در نظر گرفته شده است.
سپس وضعیت نگاشت تک تک خواص کلاس Customer را مشخص می‌کنیم. توسط Id(c => c.Id).GeneratedBy.HiLo به سیستم اعلام خواهیم کرد که فیلد Id از نوع identity است که از 1000 شروع خواهد شد. مابقی موارد هم بسیار واضح هستند. تمامی خواص کلاس Customer ذکر شده، نال را نمی‌پذیرند (منهای AddressLine2) و طول آن‌ها نیز مشخص گردیده است.
با کمک Fluent NHibernate ، بحث بررسی نوع‌های داده‌ای و همچنین یکی بودن موارد مطرح شده در نگاشت با کلاس اصلی Customer به سادگی توسط کامپایلر بررسی شده و خطاهای آتی کاهش خواهند یافت.

برای آشنایی بیشتر با lambda expressions می‌توان به مقاله زیر مراجعه کرد:
Step-by-step Introduction to Delegates and Lambda Expressions


ادامه دارد...