IEnumerable‹T› имеет результаты до тех пор, пока не будет вызван Count() или Any()

Я проверяю производительность вариантов методов расширения Linq и столкнулся со странной ситуацией.

Когда выполнение вернется к тесту, первый вызов Count() вернет 1, а последующий Any() будет ложным.

При первом вызове Any() оно истинно, а последующее Count() равно 0.

Проверка точки останова перед вызовом любого метода показывает, что есть 1 элемент, как и ожидалось, но перечисляемый объект пуст после его перечисления таким образом или путем вызова Any() или Count().

Может кто-нибудь объяснить такое поведение? Есть ли ошибка в моей реализации из-за какой-то оговорки об отложенном выполнении?

public class Thing
{
    public Guid Id { get; set; }
}

[TestClass]
public class IEnumerableExtensionsTests
{
    Guid[] thingKeys = new Guid[1] { Guid.Parse("11A1AA1A-1A11-A111-AA11-111111AA1A11") };
    System.Collections.ObjectModel.Collection<Thing> things= new System.Collections.ObjectModel.Collection<Thing>();
    int additionalThingCount = 100;

    [TestMethod]
    public void TestIntersect1()
    {
        DateTime start = DateTime.Now;
        var exceptionsList = things.Intersect1(thingKeys, (e) => e.Id);
        //int count1 = exceptionsList.Count();
        //Assert.AreEqual<int>(thingKeys.Length, count1);
        bool any1 = exceptionsList.Any();
        int count2 = exceptionsList.Count();
        bool any2 = exceptionsList.Any();
        string key = thingKeys[0].ToString();
        var first = exceptionsList.FirstOrDefault();
        var result = exceptionsList.FirstOrDefault(e => e.Id.ToString() == key);
        var duration = DateTime.Now - start;
        Debug.WriteLine(string.Format("TestIntersect1 duration {0}", duration));
        Assert.IsNotNull(result);
    }

    [TestInitialize]
    public void TestInit()
    {
        foreach(var key in thingKeys)
        {
            things.Add(new Thing()
            {
                Id = key
            });
        };
        for (int i1 = 0; i1 < additionalThingCount; i1++)
        {
            things.Add(new Thing()
            {
                Id = Guid.NewGuid()
            });
        }
    }
}

public static class IEnumerableExtension
{
    public static IEnumerable<T> Intersect1<T, Y>(this IEnumerable<T> items, IEnumerable<Y> keys, Func<T, Y> firstMemberSelector)
    {
        var hashset = new HashSet<Y>(keys);
        var returnValue = items.Where(t => hashset.Remove(firstMemberSelector(t)));
        return returnValue;
    }
}

person Chris Kissinger    schedule 26.03.2014    source источник
comment
Это исправило это (мне еще предстоит искать новые побочные эффекты в моем коде): and-a-potential-trap-to-avoid.aspx" rel="nofollow noreferrer">msmvps.com/blogs/jon_skeet/archive/2008/02/28/ var hashset = new HashSet‹Y›( ключи); var returnValue = items.Where(t =› hashset.Remove(firstMemberSelector(t))); foreach (var item in returnValue) { yield return item; }   -  person Chris Kissinger    schedule 26.03.2014


Ответы (1)


Каждый раз, когда вы перебираете результат, вы будете вызывать этот Where фильтр... который удаляет элементы из hashset по ходу дела.

Таким образом, после того, как он повторится один раз, у hashset больше не будет ни одного из этих элементов, поэтому возвращать будет нечего.

По сути, вы наблюдаете, что предложение Where с побочным эффектом - плохая идея.

Вместо этого вы можете использовать Join для выполнения пересечения:

return items.Join(keys, firstMemberSelector, key => key, (value, key) => value);

Это не совсем то же самое, поскольку это не будет операция набора... но вы потенциально можете это исправить.

person Jon Skeet    schedule 26.03.2014
comment
Именно, повторяющиеся перечисления повторяют полученный/отложенный код LINQ. Ваш код не идемпотентный. - person Haney; 26.03.2014
comment
Версия для присоединения — еще одна в том наборе, который я тестирую :). Таким образом, хэш-набор — это одна и та же копия, используемая снова и снова? - person Chris Kissinger; 26.03.2014
comment
@ChrisKissinger: Абсолютно. Вы вызываете Intersect1 только один раз и, в конце концов, не используете блок итератора... - person Jon Skeet; 26.03.2014