6

I am using the below statement with the intent of getting all of the machine objects from the MachineList collection (type IEnumerable) that have a MachineStatus of i. The MachineList collection will not always contain machines with a status of i.

At times when no machines have a MachineStatus of i I'd like to return an empty collection. My call to ActiveMachines (which is used first) works but InactiveMachines does not.

public IEnumerable<Machine> ActiveMachines
{
    get
    {
        return Customer.MachineList
            .Where(m => m.MachineStatus == "a");
    }
}

public IEnumerable<Machine> InactiveMachines
{
    get
    {
        return Customer.MachineList
            .Where(m => m.MachineStatus == "i");
    }
}

Edit

Upon further examination it appears that any enumeration of MachineList will cause subsequent enumerations of MachineList to throw an exeception: Object reference not set to an instance of an object.

Therefore, it doesn't matter if a call is made to ActiveMachines or InactiveMachines as its an issue with the MachineList collection. This is especially troubling because I can break calls to MachineList simply by enumerating it in a Watch before it is called in code. At its lowest level MachineList implements NHibernate.IQuery being returned as an IEnumerable. What's causing MachineList to lose its contents after an initial enumeration?


  • No, nothing special happens at enumeration; it would be interesting to see what "inactive" etc are in the debugger. I also wonder whether (for example) MachineStatus is a facade property that is throwing an exception - i.e. public string MachineStatus { get {return someInnerField.Status;}} and someInnerField is null. - Marc Gravell
  • The real problem is that you're enumerating an IEnumerable multiple times. The implicit contract that you agree to when you consume an IEnumerable is to enumerate it only once! So the real answer isn't for your coworker to change his code to allow multiple enumeration, but for you to do Customer.MachineList.ToList() at some point and then only use the resulting list after that (or, get a new IEnumerable again). You can do lazy evaluation; you can cache it, but don't enumerate it twice. You can also expose an IQueryable and call it multiple times, too. - ErikE

3 답변


7

Where returns an empty sequence if there are no matches; this is a perfectly valid sequence (not null). The only way you'd get a null is if you call FirstOrDefault or SingleOrDefault.

Are you sure the bug is where you think it is?

int?[] nums = { 1, 3, 5 };
var qry = nums.Where(i => i % 2 == 0);
Console.WriteLine(qry == null); // false
Console.WriteLine(qry.Count()); // 0
var list = qry.ToList();
Console.WriteLine(list.Count); // 0
var first = qry.FirstOrDefault();
Console.WriteLine(first == null); // true


  • That's what I thought. I've modified my question to reflect a modification that throws the same error. - ahsteele
  • Added some additional information to my question that might shed some light on what's going on. That said, I think I might be bordering on needing to open a new thread. - ahsteele
  • The error had to do w/ the way MachineList was being built. I had been consuming this method from another developer and hadn't checked the way it was being built up. Thanks for pointing me in the right direction by asking if I was sure I was looking in the right place. - ahsteele

4

By default, Enumerable.Where already does return an empty IEnumerable<T>, not null. If you are seeing "Object reference not set to an instance of an object." exceptions, it's most likely that something else is the problem.

Is MachineList null, perhaps? If you hadn't created it, you'd get that exception on your call to .Where(...)


  • Thanks for pointing me in the right direction by asking if I was sure I was looking in the right place. - ahsteele

2

Additionally, if you wish to explicitly return an empty collection, this can help...

Enumerable.Empty<Machine>();

Linked


Related

Latest