[C#.NET][LINQ] 在 LINQ 中,避免多餘的查詢次數的方法

[C#.NET][LINQ] 在 LINQ 中,避免多餘的查詢次數的方法

延用上一篇的類別  [C#.NET] 若要自訂義基底集合類別,應避免繼承List<T>,並且增加 IterateNumber 屬性,修改GetEnumerator方法,這是用來觀察LINQ查詢語法的查詢(Iteration)次數用,當使用LINQ查詢一次IterateNumber 就會累加一次。

public class CellPhones : IEnumerable<CellPhone>, ICollection<CellPhone>
{
    List<CellPhone> _items = new List<CellPhone>();

    public int IterateNumber { get; set; }

    public int Count
    {
        get { return this._items.Count; }
    }

    public bool IsReadOnly
    {
        get
        {
            return false;
        }
    }

    public void Add(CellPhone Item)
    {
        this._items.Add(Item);
    }

    public bool Remove(CellPhone Item)
    {
        return this._items.Remove(Item);
    }

    public void Clear()
    {
        this._items.Clear();
    }

    public bool Contains(CellPhone item)
    {
        return this._items.Contains(item);
    }

    public void CopyTo(CellPhone[] array, int arrayIndex)
    {
        this._items.CopyTo(array, arrayIndex);
    }

    public IEnumerator<CellPhone> GetEnumerator()
    {
        foreach (var item in this._items)
        {
            this.IterateNumber++;
            yield return item;
        }
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

 我先為集合添加幾筆資料

CellPhones phones = new CellPhones()
{
    new CellPhone(){CountryCode="+86",Phone="338-2817"},
    new CellPhone(){CountryCode="+86",Phone="338-2816"},
    new CellPhone(){CountryCode="+866",Phone="338-1982"},
    new CellPhone(){CountryCode="+87",Phone="776-1232"},
};

 

接下來我們常用的LINQ語法可能是這樣,猜猜看以下兩個寫法哪個效率高?

法一:

var query = from data in phones
            where data.CountryCode == "+86"
            select data;

foreach (var item in query)
{
    Console.WriteLine(item.CountryCode);
}

法二:

var query = from data in phones
            where data.CountryCode == "+86"
            select data;
var actual = query.First();

有人可能會覺得法一比較好,或許有人也覺得這兩個寫法沒什麼差。

看看接下來的測試:法一測試,IterateNumber 累加次數為4

public void QueryToListTest()
{
    CellPhones phones = new CellPhones()
    {
        new CellPhone(){CountryCode="+86",Phone="338-2817"},
        new CellPhone(){CountryCode="+86",Phone="338-2816"},
        new CellPhone(){CountryCode="+866",Phone="338-1982"},
        new CellPhone(){CountryCode="+87",Phone="776-1232"},
    };

    var query = from data in phones
                where data.CountryCode == "+86"
                select data;

    foreach (var item in query)
    {
        Console.WriteLine(item.CountryCode);
    }

    Assert.IsTrue(phones.IterateNumber >= 4);
}

 

法二測試,IterateNumber 累加次數為1

public void QueryFirstTest()
{
    CellPhones phones = new CellPhones()
    {
        new CellPhone(){CountryCode="+86",Phone="338-2817"},
        new CellPhone(){CountryCode="+86",Phone="338-2816"},
        new CellPhone(){CountryCode="+866",Phone="338-1982"},
        new CellPhone(){CountryCode="+87",Phone="776-1232"},
    };

    var query = from data in phones
                where data.CountryCode == "+86"
                select data;
    var actual = query.First();

    Assert.IsTrue(phones.IterateNumber == 1);
}

這真是令人吃驚,這表示 First 方法會在條件滿足後立即返回結果,所以兩個查詢方法所得到的 IterateNumber 不一樣,這對於大集合的搜尋會帶來很大的效能幫助。


TakeFirst 有異取同工之妙的地方,同樣都是在條件滿足後直接返回查詢,不會有多餘的Iteration,我用Take來返回滿足where條件的前兩筆資料

public void QueryTakeTest()
{
    CellPhones phones = new CellPhones()
    {
        new CellPhone(){CountryCode="+86",Phone="338-2817"},
        new CellPhone(){CountryCode="+86",Phone="338-2816"},
        new CellPhone(){CountryCode="+866",Phone="338-1982"},
        new CellPhone(){CountryCode="+87",Phone="776-1232"},
    };

    var query = from data in phones
                where data.CountryCode == "+86"
                select data;

    var take = query.Take(2);
    foreach (var item in take)
    {
        Console.WriteLine(item.Phone);
    }

    Assert.IsTrue(phones.IterateNumber == 2);
}

 


FirstOrDefaultFirst 更好!
 

接下來看看我以前會用的蠢方法(拍腦):當時為了滿足where條件,不讓First拋出例外(Note1),先調用Count()方法,再調用First()

public void QueryFirstTest1()
{
    CellPhones phones = new CellPhones()
    {
        new CellPhone(){CountryCode="+86",Phone="338-2817"},
        new CellPhone(){CountryCode="+86",Phone="338-2816"},
        new CellPhone(){CountryCode="+866",Phone="338-1982"},
        new CellPhone(){CountryCode="+87",Phone="776-1232"},
    };

    var query = from data in phones
                where data.CountryCode == "+86"
                select data;

    int count = query.Count();

    if (count >= 1)
    {
        CellPhone actual = (CellPhone)query.First();
    }

    Assert.IsTrue(phones.IterateNumber == 5);
}

這樣的寫法會IterateNumber 為5,因為Count方法跑完之後IterateNumber =4,再加上First方法,總共5次改用FirstOrDefault後,再判斷是否為null,就不會有問題了

public void QueryFirstTest1()
{
    CellPhones phones = new CellPhones()
    {
        new CellPhone(){CountryCode="+86",Phone="338-2817"},
        new CellPhone(){CountryCode="+86",Phone="338-2816"},
        new CellPhone(){CountryCode="+866",Phone="338-1982"},
        new CellPhone(){CountryCode="+87",Phone="776-1232"},
    };

    var query = (from data in phones
                 where data.CountryCode == "+8611"
                 select data).FirstOrDefault();
    if (query != null)
    {
        //TODO:做自己要做的事
    }
    Assert.IsTrue(phones.IterateNumber == 4);
}

Note1.當where條件無法滿足時,調用First方法後拋出例外。

var query = (from data in phones
			 where data.CountryCode == "+8611"
			 select data).First();

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo