170

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.

5
  • 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. Commented Jul 29, 2009 at 21:00
  • @Jon Skeet Thanks. I'll convert it to a regular class with private members. Commented Jul 29, 2009 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. Commented May 28, 2010 at 12:04
  • @Jon Skeet. Can you be more specific with the pitfalls of mutable structs, or point me to a reading. Commented May 28, 2010 at 18:37
  • 3
    @Midhat: Have a look at stackoverflow.com/questions/441309/why-are-mutable-structs-evil for a starting point. Commented May 28, 2010 at 18:43

10 Answers 10

304

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();
Sign up to request clarification or add additional context in comments.

2 Comments

I want to use distinct on 1 column and retrieve multiple columns.SO how can i do it?
I never thought to Select into a new type. In my case I selected into a new KeyValuePair.
33

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.

Comments

29
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!

Comments

17

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

1 Comment

Your solution could be simplified by removing Select like so: var items = listObject.SelectMany(f => new List<int>() { f.Item1, f.Item2 }).Distinct();
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();

1 Comment

The additional clarification on how to name the output properties is great here, thanks!
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();

1 Comment

This adds unnecessary complexity without a benefit
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.

Comments

3
(from i in list
 select new { i.category_id, i.category_name })
 .Distinct()
 .OrderBy(i => i.category_name);

Comments

3
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);
        }
    }
}

Comments

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.