164

In ASP.NET C# I have a struct:

public struct Data
{
    public int item1;
    public int item2;
    public int category_id;
    public string category_name;
}

and I have a List of those. I want to select category_id and category_name, running a DISTINCT and finally an ORDERBY on category_name.

Here's what I have now:

List<Data> listObject = getData();
string[] catNames = listObject
                    .Select(i=> i.category_name)
                    .Distinct()
                    .OrderByDescending(s => s)
                    .ToArray();

This obviously just gets the category name. My question is, how do I get multiple fields, and what data structure will I store this in (not a string[])?

EDIT

Using a list of structs is not set in stone. If it would be advisable to change my backing data structure to make selects easier (I'll be writing a lot of these) then I'd gladly take recommendations.

Chet
  • 21,375
  • 10
  • 40
  • 58
  • 11
    While it's unrelated to the LINQ side, I would *strongly* advise you not to use mutable structs or public fields. Personally I rarely create structs in the first place, but mutable structs are just asking for trouble. – Jon Skeet Jul 29 '09 at 21:00
  • @Jon Skeet Thanks. I'll convert it to a regular class with private members. – Chet Jul 29 '09 at 21:04
  • 1
    @Midhat: Mutable structs cause all kinds of problems, as they don't behave as people expect them to. And public fields give a complete lack of encapsulation. – Jon Skeet May 28 '10 at 12:04
  • @Jon Skeet. Can you be more specific with the pitfalls of mutable structs, or point me to a reading. – Midhat May 28 '10 at 18:37
  • 3
    @Midhat: Have a look at http://stackoverflow.com/questions/441309/why-are-mutable-structs-evil for a starting point. – Jon Skeet May 28 '10 at 18:43

10 Answers10

293

Anonymous types allow you to select arbitrary fields into data structures that are strongly typed later on in your code:

var cats = listObject
    .Select(i => new { i.category_id, i.category_name })
    .Distinct()
    .OrderByDescending(i => i.category_name)
    .ToArray();

Since you (apparently) need to store it for later use, you could use the GroupBy operator:

Data[] cats = listObject
    .GroupBy(i => new { i.category_id, i.category_name })
    .OrderByDescending(g => g.Key.category_name)
    .Select(g => g.First())
    .ToArray();
Jason
  • 28,040
  • 10
  • 64
  • 64
28

You could use an anonymous type:

.Select(i => new { i.name, i.category_name })

The compiler will generate the code for a class with name and category_name properties and returns instances of that class. You can also manually specify property names:

i => new { Id = i.category_id, Name = i.category_name }

You can have arbitrary number of properties.

Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
27
var selectedCategories =
    from value in
        (from data in listObject
        orderby data.category_name descending
        select new { ID = data.category_id, Name = data.category_name })
    group value by value.Name into g
    select g.First();

foreach (var category in selectedCategories) Console.WriteLine(category);

Edit: Made it more LINQ-ey!

IRBMe
  • 4,335
  • 1
  • 21
  • 22
14

You can select multiple fields using linq Select as shown above in various examples this will return as an Anonymous Type. If you want to avoid this anonymous type here is the simple trick.

var items = listObject.Select(f => new List<int>() { f.Item1, f.Item2 }).SelectMany(item => item).Distinct();

I think this solves your problem

AR M
  • 303
  • 3
  • 7
  • 1
    Your solution could be simplified by removing `Select` like so: `var items = listObject.SelectMany(f => new List() { f.Item1, f.Item2 }).Distinct();` – B8ightY Dec 07 '22 at 11:12
7

This is task for which anonymous types are very well suited. You can return objects of a type that is created automatically by the compiler, inferred from usage.

The syntax is of this form:

new { Property1 = value1, Property2 = value2, ... }

For your case, try something like the following:

var listObject = getData();
var catNames = listObject.Select(i =>
    new { CatName = i.category_name, Item1 = i.item1, Item2 = i.item2 })
    .Distinct().OrderByDescending(s => s).ToArray();
Noldorin
  • 144,213
  • 56
  • 264
  • 302
7

You can make it a KeyValuePair, so it will return a "IEnumerable<KeyValuePair<string, string>>"

So, it will be like this:

.Select(i => new KeyValuePair<string, string>(i.category_id, i.category_name )).Distinct();
Victor
  • 163
  • 3
  • 8
4
var result = listObject.Select( i => new{ i.category_name, i.category_id } )

This uses anonymous types so you must the var keyword, since the resulting type of the expression is not known in advance.

Paul van Brenk
  • 7,450
  • 2
  • 33
  • 38
3
(from i in list
 select new { i.category_id, i.category_name })
 .Distinct()
 .OrderBy(i => i.category_name);
Joe Chung
  • 11,955
  • 1
  • 24
  • 33
2
public class Student
{
    public string Name { set; get; }
    public int ID { set; get; }
}

class Program
{
  static void Main(string[] args)
    {
        Student[] students =
        {
        new Student { Name="zoyeb" , ID=1},
        new Student { Name="Siddiq" , ID=2},
        new Student { Name="sam" , ID=3},
        new Student { Name="james" , ID=4},
        new Student { Name="sonia" , ID=5}
        };

        var studentCollection = from s in students select new { s.ID , s.Name};

        foreach (var student in studentCollection)
        {
            Console.WriteLine(student.Name);
            Console.WriteLine(student.ID);
        }
    }
}
Zoyeb Shaikh
  • 301
  • 1
  • 11
1

Given List<MyType1> internalUsers and List<MyType2> externalUsers, based on the shared key of an email address...


For C# 7.0+:

var matches = (
    from i in internalUsers
    join e in externalUsers
    on i.EmailAddress.ToUpperInvariant() equals e.Email.ToUpperInvariant()
    select (internalUser:i, externalUser:e)
).ToList();

Which gives you matches as a List<(MyType1, MyType2)>.

From there you can compare them if you wish:

var internal_in_external = matches.Select(m => m.internalUser).ToList();
var external_in_internal = matches.Select(m => m.externalUser).ToList();

var internal_notIn_external = internalUsers.Except(internal_in_external).ToList();
var external_notIn_internal = externalUsers.Except(external_in_internal).ToList();

internal_in_external and internal_notIn_external will be of type List<MyType1>.

external_in_internal and external_notIn_internal will be of type List<MyType2>


For versions of C# prior to 7.0:

var matches = (
    from i in internalUsers
    join e in externalUsers
    on i.EmailAddress.ToUpperInvariant() equals e.Email.ToUpperInvariant()
    select new Tuple<MyType1, MyType2>(i, e)
).ToList();

Which gives you matches as a List<Tuple<MyType1, MyType2>>.

From there you can compare them if you wish:

var internal_in_external = matches.Select(m => m.Item1).ToList();
var external_in_internal = matches.Select(m => m.Item2).ToList();

var internal_notIn_external = internalUsers.Except(internal_in_external).ToList();
var external_notIn_internal = externalUsers.Except(external_in_internal).ToList();

internal_in_external and internal_notIn_external will be of type List<MyType1>.

external_in_internal and external_notIn_internal will be of type List<MyType2>

Danny Beckett
  • 20,529
  • 24
  • 107
  • 134