در ابتدا مثالهاي زير را در نظر بگيريد:
using System;
using System.Collections.Generic;
using System.Linq;
namespace testWinForms87
{
public class Data
{
public int id { get; set; }
public string name { get; set; }
}
class CLinqTests
{
public static int TestGetListMin1()
{
var lst = new List<Data>
{
new Data{ id=1, name="id1"},
new Data{ id=2, name="id2"},
new Data{ id=3, name="name3"}
};
return (from c in lst
where c.name.Contains("id")
select c.id).Min();
}
public static int TestGetListMin2()
{
var lst = new List<Data>();
return (from c in lst
where c.name.Contains("id")
select c.id).Min();
}
}
}
محاسبات آن كار ميكند و مشكلي هم ندارد. اما هميشه در دنياي واقعي همه چيز قرار نيست به اين خوبي پيش برود. ممكن است همانند متد TestGetListMin2 ، ليست ما خالي باشد (براي مثال از ديتابيس، ركوردي مطابق شرايط كوئريهاي قبلي بازگشت داده نشده باشد). در اين حالت هنگام فراخواني متد Min ، استثناي Sequence contains no elements رخ خواهد داد و همانطور كه در مباحث defensive programming عنوان شد، وظيفهي ما اين نيست كه خودرو را به ديوار كوبيده (يا منتظر شويم تا كوبيده شود) و سپس به فكر چاره بيفتيم كه خوب، عجب! مشكلي رخ داده است!
اكنون چه بايد كرد؟ حداقل يك مرحله بررسي اينكه آيا كوئري ما حاوي ركوردي ميباشد يا خير بايد به اين متد اضافه شود (به صورت زير):
public static int TestGetListMin3()
{
var lst = new List<Data>();
var query = from c in lst
where c.name.Contains("id")
select c.id;
if (query.Any())
return query.Min();
else
return -1;
}
شبيه به اين مورد در هنگام استفاده از تابع Single مربوط به LINQ نيز ممكن است رخ دهد (توليد استثناي ذكر شده) اما در اينجا مايكروسافت تابع SingleOrDefault را نيز پيش بيني كرده است. در اين حالت اگر كوئري ما ركوردي را برنگرداند، SingleOrDefault مقدار نال را برگشت داده و استثنايي رخ نخواهد داد (نمونهي ديگر آن متدهاي First و FirstOrDefault هستند).
در مورد متدهاي Min و Max ، متدهاي MinOrDefault يا MaxOrDefault در دات نت فريم ورك وجود ندارند. ميتوان اين نقيصه را با استفاده از extension methods برطرف كرد.
using System;
using System.Collections.Generic;
using System.Linq;
public static class LinqExtensions
{
public static T MinOrDefault<T>(this IEnumerable<T> source, T defaultValue)
{
if (source.Any<T>())
return source.Min<T>();
return defaultValue;
}
public static T MaxOrDefault<T>(this IEnumerable<T> source, T defaultValue)
{
if (source.Any<T>())
return source.Max<T>();
return defaultValue;
}
}
public static int TestGetListMin4()
{
var lst = new List<Data>();
return (from c in lst
where c.name.Contains("id")
select c.id).MinOrDefault(-1);
}