6

LINQ Improvements in .NET

 3 years ago
source link: https://code-maze.com/dotnet-linq-improvements/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

LINQ Improvements in .NET

Publisher Logo

We value your privacy

We and our store and/or access information on a device, such as cookies and process personal data, such as unique identifiers and standard information sent by a device for personalised ads and content, ad and content measurement, and audience insights, as well as to develop and improve products. With your permission we and our partners may use precise geolocation data and identification through device scanning. You may click to consent to our and our partners’ processing as described above. Alternatively you may click to refuse to consent or access more detailed information and change your preferences before consenting. Please note that some processing of your personal data may not require your consent, but you have a right to object to such processing. Your preferences will apply to this website only. You can change your preferences at any time by returning to this site or visit our privacy policy.

LINQ Improvements in .NET

Posted by Code Maze | Updated Date Aug 2, 2022 | 1

Code Maze Book Collection

Want to build great APIs? Or become even better at it? Check our Ultimate ASP.NET Core Web API program and learn how to create a full production-ready ASP.NET Core API using only the latest .NET technologies. Bonus materials (Security book, Docker book, and other bonus files) are included in the Premium package!

In this article, we will discuss the new LINQ improvements introduced in .NET 6.  We will not talk about the LINQ itself because we already have an article on that topic.

To download the source code for this article, you can visit our GitHub repository.

So, let’s start.

Batching Support Using Chunk

Often when we work with an extensive collection of objects, we might have to split the collection into several smaller arrays. .NET introduces a new method in LINQ that now supports batching sequences into Size slices. This is the Chunk method:

public static IEnumerable<T[]> Chunk(this IEnumerable<T> source, int size);
public static IEnumerable<T[]> Chunk(this IEnumerable<T> source, int size);

This method takes an enumerable and the size as arguments and splits the given enumerable into chunks of the given size:

public static IList<Student> Students => new List<Student>()
new Student("John", "CS", 10),
new Student("James", "CS", 6),
new Student("Mike", "IT", 8),
new Student("Stokes", "IT", 0),
public static List<Student[]> Chunk(int pageSize)
var studentChunks = Students.Chunk(pageSize);
return studentChunks.ToList();
public static IList<Student> Students => new List<Student>()
{
    new Student("John", "CS", 10),
    new Student("James", "CS", 6),
    new Student("Mike", "IT", 8),
    new Student("Stokes", "IT", 0),
};

public static List<Student[]> Chunk(int pageSize)
{
    var studentChunks =  Students.Chunk(pageSize);
    return studentChunks.ToList();
}

In our Program class, we can call this Chunk method and pass 2 as the pageSize.

var studentChunks = LINQUtils.Chunk(2);
foreach (var studentChunk in studentChunks)
Console.WriteLine(studentChunk.Count());
var studentChunks = LINQUtils.Chunk(2);
foreach (var studentChunk in studentChunks)
{
    Console.WriteLine(studentChunk.Count());
}

This will now divide the collection into arrays of size 2 each:

2
2

This new feature is helpful when we are trying to implement a paged API or send large data to a database.

LINQ Improvements with the MaxBy and MinBy Methods

Two new extension methods MaxBy and MinBy are introduced in LINQ to query the sequence for a maximum or minimum element that satisfies a given selector condition. The new methods take a selector query as an input and return the largest or the smallest element that satisfies the condition:

public static TSource MinBy<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
public static TSource MaxBy<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);
public static TSource MinBy<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);

public static TSource MaxBy<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector);

Let’s take a look at a simple example that returns a list of students with the highest and lowest grades:

public static Student? MaxGrade()
return Students.MaxBy(student => student.Grade);
public static Student? MinGrade()
return Students.MinBy(student => student.Grade);
public static Student? MaxGrade()
{
    return Students.MaxBy(student => student.Grade);
}

public static Student? MinGrade()
{
    return Students.MinBy(student => student.Grade);
}

This will return the student object with maximum and minimum grades:

Name: John Dept: CS Grade: 10
Name: Stokes Dept: IT Grade: 0
Name: John Dept: CS Grade: 10
Name: Stokes Dept: IT Grade: 0

Support For Default Parameters

LINQ methods like FirstOrDefault,SingleOrDefault, and LastOrDefault return the object that satisfies the condition and if there is no match, they return default values. You can read more about these methods in our LINQ Basics article.

However, with the improvements introduced in LINQ, we can now specify a default value to return from these methods. For example, in the specified student’s collection, we can now specify the method to return a new student instance instead of null, if the condition is not met:

static Student defaultStudent = new Student(name: "", department: "", grade: -1);
public static Student FirstOrDefaultStudent()
return Students.FirstOrDefault(student => student.Name.Equals("Fake Student"), defaultStudent);
public static Student LastOrDefaultStudent()
return Students.LastOrDefault(student => student.Name.Equals("Fake Student"), defaultStudent);
public static Student SingleOrDefaultStudent()
return Students.SingleOrDefault(student => student.Name.Equals("Fake Student"), defaultStudent);
static Student defaultStudent = new Student(name: "", department: "", grade: -1);

public static Student FirstOrDefaultStudent()
{
    return Students.FirstOrDefault(student => student.Name.Equals("Fake Student"), defaultStudent);
}

public static Student LastOrDefaultStudent()
{
    return Students.LastOrDefault(student => student.Name.Equals("Fake Student"), defaultStudent);
}

public static Student SingleOrDefaultStudent()
{
    return Students.SingleOrDefault(student => student.Name.Equals("Fake Student"), defaultStudent);
}

This will return the default student in all three methods:

Name: Dept: Grade: -1
Name: Dept: Grade: -1
Name: Dept: Grade: -1
Name:  Dept:  Grade: -1
Name:  Dept:  Grade: -1
Name:  Dept:  Grade: -1

This new improvement helps a lot in avoiding a NullReferenceException in .NET.

Index and Range Arguments

Index and Range arguments are already available in C#. Starting from .NET 6, they are now available for LINQ. It now supports negative Index for ElementAt and Range for Take methods.

Previously, if we wanted to get something from the end of the sequence, we had to calculate the length and subtract the index from the size. We can now use the ^ operator to get the index from the end of the list:

public static Student ElementAt(Index index)
return Students.ElementAt(index);
var student = LINQUtils.ElementAt(^3);
Console.WriteLine(student);
public static Student ElementAt(Index index)
{
    return Students.ElementAt(index);
}

var student = LINQUtils.ElementAt(^3); 
Console.WriteLine(student);

This will return the 3rd element from the end of the list:

Name: James Dept: CS Grade: 6
Name: James Dept: CS Grade: 6

The Range struct  is now referenced in LINQ for the Take method. We can now take elements that fall within a range:

public static List<Student> Take(Range range)
return Students.Take(range).ToList();
var studentsRange = LINQUtils.Take(1..3);
PrintEnumerable(studentsRange);
public static List<Student> Take(Range range)
{
    return Students.Take(range).ToList();
}

var studentsRange = LINQUtils.Take(1..3); 
PrintEnumerable(studentsRange);

This will return all the elements between indexes 1 and 3:

Name: James Dept: CS Grade: 6
Name: Mike Dept: IT Grade: 8
Name: James Dept: CS Grade: 6
Name: Mike Dept: IT Grade: 8

Get Count Without Enumeration

When we call the Count() method, LINQ checks if the count is readily available and returns it. However, if the count is not available, then it will enumerate the entire sequence and return the count. But this might cause a lot of time in some cases if an IQueryable is involved.

To avoid this enumeration, a new method TryGetNonEnumeratedCount has been introduced to get the count of sequences without enumerating the entire sequence. In case the count is not available, then it will return false and assign zero to the out variable:

public static bool CountStudents(out int count)
var queryableStudents = Students.AsQueryable();
return queryableStudents.TryGetNonEnumeratedCount(out count);
public static bool CountStudents(out int count)
{
    var queryableStudents = Students.AsQueryable();

    return queryableStudents.TryGetNonEnumeratedCount(out count);
}

Now, let’s call this method in the Program class:

int count = -1;
var doesCountExist = LINQUtils.CountStudents(out count);
Console.WriteLine(doesCountExist);
int count = -1; 
var doesCountExist = LINQUtils.CountStudents(out count); 
Console.WriteLine(doesCountExist);

Here, we are trying to get the count of students collection but since we are implementing it as an IQueryable, this will return false:

False
False

It will also set the count variable to 0 (zero). Of course, if we remove the AsQueryable method and use the Students collection as is, the method will return true and set the count to 4.

Zip With 3 IEnumerable

Previously, the Zip method was used for enumerating two sequences together. Starting from .NET 6, the Zip extension method supports up to 3 sequences:

public static IEnumerable<(string, string, int)> ZipEnumerables(List<string> names, List<string> departments, List<int> grades)
return names.Zip(departments, grades);
public static IEnumerable<(string, string, int)> ZipEnumerables(List<string> names, List<string> departments, List<int> grades)
{
    return names.Zip(departments, grades);
}

Once we call this method:

var names = new List<string>() { "John", "James", "Mike" };
var departments = new List<string>() { "ME", "AP", "IT" };
var grades = new List<int>() { 10, 6, 8 };
var enumeratedList = LINQUtils.ZipEnumerables(names, departments, grades);
PrintEnumerable(enumeratedList);
var names = new List<string>() { "John", "James", "Mike" }; 
var departments = new List<string>() { "ME", "AP", "IT" }; 
var grades = new List<int>() { 10, 6, 8 }; 

var enumeratedList = LINQUtils.ZipEnumerables(names, departments, grades); 
PrintEnumerable(enumeratedList);

As a result, we will have multiple tuples with name, department, and grade as values:

(John, ME, 10)
(James, AP, 6)
(Mike, IT, 8)
(John, ME, 10)
(James, AP, 6)
(Mike, IT, 8)

Support for Set Operations

The existing set-based methods Distinct, Except, Intersect, and Union are now improved by LINQ. It now includes support for key-based selectors with the four set-based operations namely DistinctBy, ExceptBy, IntersectBy, and UnionBy

DistinctBy

We use DistinctBy when we want to get a distinct list of elements that satisfy a key selector. For example, let’s say we want to get a list of students who pursue studies in distinct departments:

public static IEnumerable<Student> DistinctByDepartment()
return Students.DistinctBy(student => student.Department);
public static IEnumerable<Student> DistinctByDepartment()
{
    return Students.DistinctBy(student => student.Department);
}

This will now return a student for each distinct department:

Name: John Dept: CS Grade: 10
Name: Mike Dept: IT Grade: 8
Name: John Dept: CS Grade: 10
Name: Mike Dept: IT Grade: 8

ExceptBy

Similar to ExceptExceptBy returns the sequence of elements that are present in the first enumerable but not in the second. One additional advantage of using the ExceptBy is that we can use a key selector to choose on which property we can run the comparison. Since we want the key comparison for departments, we need to select the department from the second list along with the key selector:

public static IEnumerable<Student> ExceptByDepartment(List<Student> secondList)
return Students.ExceptBy(secondList.Select(student => student.Department), student => student.Department);
public static IEnumerable<Student> ExceptByDepartment(List<Student> secondList)
{
    return Students.ExceptBy(secondList.Select(student => student.Department), student => student.Department);
}

We can now return the list of students with the distinct department which is not present in the list of students we pass as the argument:

var studentsList = new List<Student>()
new Student("Perry", "CE", 10),
new Student("Dottin", "CU", 6),
new Student("Sciver", "ME", 8),
new Student("Mahesh", "IT", 3),
var unCommonStudents = LINQUtils.ExceptByDepartment(studentsList);
PrintEnumerable(unCommonStudents);
var studentsList = new List<Student>()
{
    new Student("Perry", "CE", 10),
    new Student("Dottin", "CU", 6),
    new Student("Sciver", "ME", 8),
    new Student("Mahesh", "IT", 3),
};

var unCommonStudents = LINQUtils.ExceptByDepartment(studentsList);
PrintEnumerable(unCommonStudents);

The CS department appears twice in the first list of students but not in the second. However, since this is a set operation it will only consider distinct values and hence will return the first student to the CS department:

Name: John Dept: CS Grade: 10
Name: John Dept: CS Grade: 10

IntersectBy

As the name suggests, we use IntersectBy when we want to get the intersection of two sequences. Similar to other By methods, this method also looks for the intersection of the key selectors:

public static IEnumerable<Student> IntersectByDepartment(List<Student> secondList)
return Students.IntersectBy(secondList.Select(student => student.Department), student => student.Department);
public static IEnumerable<Student> IntersectByDepartment(List<Student> secondList)
{
    return Students.IntersectBy(secondList.Select(student => student.Department), student => student.Department);
}

This will return the list of distinct students whose department is common in both the lists:

Name: John Dept: CS Grade: 10
Name: Mike Dept: IT Grade: 8
Name: John Dept: CS Grade: 10
Name: Mike Dept: IT Grade: 8

UnionBy

We use UnionBy for combining two or more sequences into a single sequence using a key selector. For example, if we use the department as our key selector, the combined list will contain distinct elements for all the departments from both the sequences :

public static IEnumerable<Student> UnionByDepartment(List<Student> secondList)
return Students.UnionBy(secondList, student => student.Department);
public static IEnumerable<Student> UnionByDepartment(List<Student> secondList)
{
    return Students.UnionBy(secondList, student => student.Department);
}

This will print the list of the student names with distinct departments from both the lists for all the departments:

Name: John Dept: CS Grade: 10
Name: Mike Dept: IT Grade: 8
Name: Perry Dept: CE Grade: 10
Name: Dottin Dept: CU Grade: 6
Name: Sciver Dept: ME Grade: 8
Name: John Dept: CS Grade: 10
Name: Mike Dept: IT Grade: 8
Name: Perry Dept: CE Grade: 10
Name: Dottin Dept: CU Grade: 6
Name: Sciver Dept: ME Grade: 8

Conclusion

In this article, we discussed a lot about the new LINQ methods as well as the improvements to the existing ones. We also learned about their use cases along with examples.

Code Maze Book Collection

Want to build great APIs? Or become even better at it? Check our Ultimate ASP.NET Core Web API program and learn how to create a full production-ready ASP.NET Core API using only the latest .NET technologies. Bonus materials (Security book, Docker book, and other bonus files) are included in the Premium package!

Share:

Subscribe
guest
Label
1 Comment
Oldest
asp.net-core-web-api-best-practices-image.png

Get our free, easy Web API best practices guide
used by 20k+ people to become better web devs.

Leave this field empty if you're human:

© Copyright code-maze.com 2016 - 2022

wpDiscuz

</body


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK