Linq oddity?

C

Chris Dunaway

The following code is placed a new Windows Forms App and a
DataGridView and BindingSource are dragged onto the form:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ThrowAwayCS
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

protected override void OnShown(EventArgs e)
{
base.OnShown(e);

dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = bindingSource1;

var procs = from process in
System.Diagnostics.Process.GetProcesses()
select process.ProcessName;

bindingSource1.DataSource = procs;
}
}
}

When run, the grid does not show a ProcessName column with the names
of the processes as expected, it has a Length column with the lengths
of the names.

However, change the LINQ to the following and the correct values are
shown:

var procs = from process in
System.Diagnostics.Process.GetProcesses()
select new {process.ProcessName};

Can anyone shed some light on this? I am sure I am missing something
obvious.

Thanks,

Chris
 
J

Jon Skeet [C# MVP]

When run, the grid does not show a ProcessName column with the names
of the processes as expected, it has a Length column with the lengths
of the names.

However, change the LINQ to the following and the correct values are
shown:

var procs = from process in
System.Diagnostics.Process.GetProcesses()
select new {process.ProcessName};

Can anyone shed some light on this? I am sure I am missing something
obvious.

First version:
Type of data is a string, only public property is Length

Second version:
Type of data is an anonymous type, public property is ProcessName

I'm guessing that a BindingSource looks through the properties of the
type of data that's provided to it, and displays those. You basically
want to display the whole item, giving a column of "Process Name", I
suspect.

I'm afraid I don't know enough about how data binding is meant to work,
but I suspect the above explains your results to some extent :)
 
C

Chris Dunaway

var procs = from process in
System.Diagnostics.Process.GetProcesses()
select process.ProcessName;


Ok, continuing the discussion, using the query above as a start, what
is procs after I'm done? Is it an IEnumerable<string>? Secondly, how
would I select processes with a unique name?

I tried this:

var uniqueProcs =
System.Diagnostics.Process.GetProcesses().SelectMany(p =>
p.ProcessName).Distinct();

But that doesn't produce what I thought.

Finally, why didn't the C# team include all the query operators like
the VB team did? I hope they will be included in the future.

Thanks,

Chris
 
J

Jon Skeet [C# MVP]

Chris Dunaway said:
var procs = from process in
System.Diagnostics.Process.GetProcesses()
select process.ProcessName;


Ok, continuing the discussion, using the query above as a start, what
is procs after I'm done? Is it an IEnumerable<string>?

Secondly, how would I select processes with a unique name?

Do you mean select a distinct set of process names?

var uniqueProcs = Process.GetProcesses()
.Select(proc => proc.ProcessName)
.Distinct();
I tried this:

var uniqueProcs =
System.Diagnostics.Process.GetProcesses().SelectMany(p =>
p.ProcessName).Distinct();

But that doesn't produce what I thought.

Hmm... I suspect that gave you back a distinct sequence of all the
*characters* which are in any of the processes.

SelectMany takes each element of the original sequence and generates a
subsequence *per element*. The overload you've used just flattens that
resulting subsequence. Now, the delegate you've used to convert an
original element (Process) to a subsequence is to take the process name
- which is a string, i.e. a sequence of characters.
Finally, why didn't the C# team include all the query operators like
the VB team did? I hope they will be included in the future.

I hope they won't. I like a simple language with a big library behind
it. The existing query expressions already complicate the language, but
they cover the biggest uses. Calling methods explicitly when they don't
cover it is reasonable to me - but adding yet more complexity to the
language would be a mistake IMO.
 
C

Chris Dunaway

Do you mean select a distinct set of process names?

var uniqueProcs = Process.GetProcesses()
.Select(proc => proc.ProcessName)
.Distinct();

Thanks, here's what I ended up with and it returned the results I
expected. However, curiously, the VB version did not seem to work:

C# version works (new keyword used so data binding will work
correctly):

var uniqueProcs = Process.GetProcesses()
.OrderBy(p => p.ProcessName)
.Select(p => new { p.ProcessName })
.Distinct();

VB version doesn't work (New keyword used so data binding will work
correctly):

Dim uniqueProcs = Process.GetProcesses() _
.OrderBy(Function(p) p.ProcessName) _
.Select(Function(p) New With {p.ProcessName}) _
.Distinct()

And when I say it doesn't work, I mean that it returns a list, but the
duplicates are not removed.
I hope they won't. I like a simple language with a big library behind
it. The existing query expressions already complicate the language, but
they cover the biggest uses. Calling methods explicitly when they don't
cover it is reasonable to me - but adding yet more complexity to the
language would be a mistake IMO.

I agree, but I still think having at least distinct, skip, and take
would be nice.

An expression like this seems a little nicer than using the extension
methods with lambdas, not that the latter is too difficult to read
though.

//Non working code
var procs = from process in Process.GetProcesses()
orderby process.ProcessName
select new { process.ProcessName }
distinct;


Cheers,

Chris
 
J

Jon Skeet [C# MVP]

Chris Dunaway said:
Thanks, here's what I ended up with and it returned the results I
expected. However, curiously, the VB version did not seem to work:

C# version works (new keyword used so data binding will work
correctly):

var uniqueProcs = Process.GetProcesses()
.OrderBy(p => p.ProcessName)
.Select(p => new { p.ProcessName })
.Distinct();

VB version doesn't work (New keyword used so data binding will work
correctly):

Dim uniqueProcs = Process.GetProcesses() _
.OrderBy(Function(p) p.ProcessName) _
.Select(Function(p) New With {p.ProcessName}) _
.Distinct()

And when I say it doesn't work, I mean that it returns a list, but the
duplicates are not removed.

This is because by default, VB anonymous types are mutable. Change it
to
New With { Key p.Processname }
and I think you'll find it's okay.

Frankly I find this choice a bizarre one on the part of the VB team.
The rest of the world is running towards immutability, and VB decides
to default to mutability...
I agree, but I still think having at least distinct, skip, and take
would be nice.

An expression like this seems a little nicer than using the extension
methods with lambdas, not that the latter is too difficult to read
though.

//Non working code
var procs = from process in Process.GetProcesses()
orderby process.ProcessName
select new { process.ProcessName }
distinct;

I don't think that's much better than:

var procs = (from process in Process.GetProcesses()
orderby process.ProcessName
select new { process.ProcessName })
.Distinct();

The difference is a mere 5 characters - not a lot compared with the
complexity cost to the language. Note that the existing query
expression clauses all save redundancy in terms of expressing a lambda
expression.

I guess we'll have to agree to differ though.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top