Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSharpNotesForProfessionals.pdf
Скачиваний:
65
Добавлен:
20.05.2023
Размер:
6.12 Mб
Скачать

int[] numbers = { 1, 2, 3, 4, 5 }; numbers.ElementAtOrDefault(2); // 3 numbers.ElementAtOrDefault(10); // 0 = default(int)

Both ElementAt and ElementAtOrDefault are optimized for when the source is an IList<T> and normal indexing will be used in those cases.

Note that for ElementAt, if the provided index is greater than the size of the IList<T>, the list should (but is technically not guaranteed to) throw an ArgumentOutOfRangeException.

Section 66.35: DefaultIfEmpty

DefaultIfEmpty is used to return a Default Element if the Sequence contains no elements. This Element can be the Default of the Type or a user defined instance of that Type. Example:

var chars = new List<string>() { "a", "b", "c", "d" };

chars.DefaultIfEmpty("N/A").FirstOrDefault(); // returns "a";

chars.Where(str => str.Length > 1)

.DefaultIfEmpty("N/A").FirstOrDefault(); // return "N/A"

chars.Where(str => str.Length > 1)

.DefaultIfEmpty().First(); // returns null;

Usage in Left Joins:

With DefaultIfEmpty the traditional Linq Join can return a default object if no match was found. Thus acting as a SQL's Left Join. Example:

var leftSequence = new List<int>() { 99, 100, 5, 20, 102, 105 }; var rightSequence = new List<char>() { 'a', 'b', 'c', 'i', 'd' };

var numbersAsChars = from l in leftSequence join r in rightSequence

on l equals (int)r into leftJoin

from result in leftJoin.DefaultIfEmpty('?') select new

{

Number = l, Character = result

};

foreach(var item in numbersAsChars)

{

Console.WriteLine("Num = {0} ** Char = {1}", item.Number, item.Character);

}

output:

 

Num = 99

Char = c

Num = 100

Char = d

Num = 5

Char = ?

Num = 20

Char = ?

Num = 102

Char = ?

Num = 105

Char = i

In the case where a DefaultIfEmpty is used (without specifying a default value) and that will result will no matching

GoalKicker.com – C# Notes for Professionals

378

items on the right sequence one must make sure that the object is not null before accessing its properties. Otherwise it will result in a NullReferenceException. Example:

var leftSequence = new List<int> { 1, 2, 5 }; var rightSequence = new List<dynamic>()

{

new { Value = 1 }, new { Value = 2 }, new { Value = 3 }, new { Value = 4 },

};

var numbersAsChars = (from l in leftSequence join r in rightSequence

on l equals r.Value into leftJoin

from result in leftJoin.DefaultIfEmpty() select new

{

Left = l,

//5 will not have a matching object in the right so result

//will be equal to null.

//To avoid an error use:

//- C# 6.0 or above - ?.

// - Under - result == null ? 0 : result.Value

Right = result?.Value }).ToList();

Section 66.36: ToDictionary

The ToDictionary() LINQ method can be used to generate a Dictionary<TKey, TElement> collection based on a

given IEnumerable<T> source.

IEnumerable<User> users = GetUsers();

Dictionary<int, User> usersById = users.ToDictionary(x => x.Id);

In this example, the single argument passed to ToDictionary is of type Func<TSource, TKey>, which returns the key for each element.

This is a concise way to perform the following operation:

Dictionary<int, User> usersById = new Dictionary<int User>(); foreach (User u in users)

{

usersById.Add(u.Id, u);

}

You can also pass a second parameter to the ToDictionary method, which is of type Func<TSource, TElement>

and returns the Value to be added for each entry.

IEnumerable<User> users = GetUsers();

Dictionary<int, string> userNamesById = users.ToDictionary(x => x.Id, x => x.Name);

It is also possible to specify the IComparer that is used to compare key values. This can be useful when the key is a string and you want it to match case-insensitive.

IEnumerable<User> users = GetUsers();

Dictionary<string, User> usersByCaseInsenstiveName = users.ToDictionary(x => x.Name,

GoalKicker.com – C# Notes for Professionals

379

StringComparer.InvariantCultureIgnoreCase);

var user1 = usersByCaseInsenstiveName["john"]; var user2 = usersByCaseInsenstiveName["JOHN"]; user1 == user2; // Returns true

Note: the ToDictionary method requires all keys to be unique, there must be no duplicate keys. If there are, then an exception is thrown: ArgumentException: An item with the same key has already been added. If you have a scenario where you know that you will have multiple elements with the same key, then you are better o using

ToLookup instead.

Section 66.37: Concat

Merges two collections (without removing duplicates)

List<int> foo = new List<int> { 1, 2, 3 };

List<int> bar = new List<int> { 3, 4, 5 };

// Through Enumerable static class

var result = Enumerable.Concat(foo, bar).ToList(); // 1,2,3,3,4,5

// Through extension method

var result = foo.Concat(bar).ToList(); // 1,2,3,3,4,5

Section 66.38: Build your own Linq operators for IEnumerable<T>

One of the great things about Linq is that it is so easy to extend. You just need to create an extension method whose argument is IEnumerable<T>.

public namespace MyNamespace

{

public static class LinqExtensions

{

public static IEnumerable<List<T>> Batch<T>(this IEnumerable<T> source, int batchSize)

{

var batch = new List<T>(); foreach (T item in source)

{

batch.Add(item);

if (batch.Count == batchSize)

{

yield return batch; batch = new List<T>();

}

}

if (batch.Count > 0) yield return batch;

}

}

}

This example splits the items in an IEnumerable<T> into lists of a fixed size, the last list containing the remainder of the items. Notice how the object to which the extension method is applied is passed in (argument source) as the initial argument using the this keyword. Then the yield keyword is used to output the next item in the output IEnumerable<T> before continuing with execution from that point (see yield keyword).

GoalKicker.com – C# Notes for Professionals

380

This example would be used in your code like this:

//using MyNamespace;

var items = new List<int> { 2, 3, 4, 5, 6 }; foreach (List<int> sublist in items.Batch(3))

{

// do something

}

On the first loop, sublist would be {2, 3, 4} and on the second {5, 6}.

Custom LinQ methods can be combined with standard LinQ methods too. e.g.:

//using MyNamespace;

 

 

var result = Enumerable.Range(0, 13)

//

generate a list

.Where(x => x%2 == 0) //

filter the list or do something other

.Batch(3)

//

call our extension method

.ToList()

//

call other standard methods

 

 

 

This query will return even numbers grouped in batches with a size of 3: {0, 2, 4}, {6, 8, 10}, {12}

Remember you need a using MyNamespace; line in order to be able to access the extension method.

Section 66.39: Select - Transforming elements

Select allows you to apply a transformation to every element in any data structure implementing IEnumerable. Getting the first character of each string in the following list:

List<String> trees = new List<String>{ "Oak", "Birch", "Beech", "Elm", "Hazel", "Maple" };

Using regular (lambda) syntax

//The below select stament transforms each element in tree into its first character.

IEnumerable<String> initials = trees.Select(tree => tree.Substring(0, 1)); foreach (String initial in initials) {

System.Console.WriteLine(initial);

}

Output:

O

B

B

E

H

M

Live Demo on .NET Fiddle

Using LINQ Query Syntax

initials = from tree in trees

select tree.Substring(0, 1);

GoalKicker.com – C# Notes for Professionals

381