AbrahamJP
AbrahamJP

Reputation: 3440

LINQ Casting issue

I have a winform with two checkboxes and a button. On the CheckedChanged event of both checkboxes i had given the following code.

//Enable the button if any of the checkbox is checked
var ChkBoxes = from CheckBox ctrl in this.Controls 
               where ctrl is CheckBox select ctrl;
button1.Enabled = ChkBoxes.Any(c => ((CheckBox)c).Checked);

but when checking either of the checkboxes I am getting an error "Unable to cast object of type 'System.Windows.Forms.Button' to type 'System.Windows.Forms.CheckBox'." the error comes up while executing the second line of code.

Later I updated the code to the following, which works fine. The only change I made is modified ctrl type from CheckBox to Control.

var ChkBoxes = from Control ctrl in this.Controls 
               where ctrl is CheckBox select ctrl;
button1.Enabled = ChkBoxes.Any(c => ((CheckBox)c).Checked);

My question is, in both cases I am returning controls only of type checkbox, then how come the cast error comes up. Can anyone explain me how this works?

Upvotes: 2

Views: 1050

Answers (5)

Because it gets converted to something like this:

IEnumerable<CheckBox> ChkBoxes = this.Controls.Cast<CheckBox>().Where(ctrl => ctrl is CheckBox).Select(o => o);

Note that the cast to CheckBox gets executed before the check that ensures it's valid (the error occurs on the last line due to deferred execution).

To fix this, specify Control rather than CheckBox as you mentioned, or, even more succinctly, use OfType():

var ChkBoxes = this.Controls.OfType<CheckBox>();

Upvotes: 3

Jeff Mercado
Jeff Mercado

Reputation: 134611

The Controls collection is an IEnumerable which holds Controls of different types not necessarily a CheckBox.

You need to cast to some common base type of these controls (Control) then filter the collection to CheckBox objects.

var query = from Control ctrl in this.Controls
            where ctrl is CheckBox
            select (CheckBox)ctrl; // cast to CheckBox here instead

// which is equivalent to:
var q = this.Controls
            .Cast<Control>()
            .Where(ctrl => ctrl is CheckBox)
            .Select(ctrl => (CheckBox)ctrl);

Or filter it directly using the OfType<T>() extension method. The result is a strongly typed enumerable IEnumerable<CheckBox>. I would recommend this approach.

var query = this.Controls.OfType<CheckBox>();

Upvotes: 1

asawyer
asawyer

Reputation: 17808

In the first example the exception isnt thrown because a Linq query uses what's called "Deffered" or "Lazy" evaluation.

It only started getting values for you when the second line was run, called "Enumerating" the query.

Edit - The OfType extension method from the other answer is probably the way to go.

Upvotes: 1

Reed Copsey
Reed Copsey

Reputation: 564861

Instead of using:

var ChkBoxes = from CheckBox ctrl in this.Controls where ctrl is CheckBox select ctrl;

Try using Enumerable.OfType<T> to do your filtering:

var chkBoxes = this.Controls.OfType<CheckBox>();
button1.Enabled = chkBoxes.Any(c => c.Checked); // No cast required now

Upvotes: 6

quip
quip

Reputation: 3694

Because 'this.Controls' holds Controls, all of them, on the form. The where clause has not yet been applied.

So before Linq can work with your where clause, it tried to cast the object to the type you specified, which was originally CheckBox. When it got to a Button, the cast failed.

In your fix, you correctly used the Control type, which is valid since all objects in 'this.Controls' are Control, and then Linq can apply the rest of the Linq statement.

Upvotes: 1

Related Questions