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

Section 66.18: Enumerating the Enumerable

The IEnumerable<T> interface is the base interface for all generic enumerators and is a quintessential part of understanding LINQ. At its core, it represents the sequence.

This underlying interface is inherited by all of the generic collections, such as Collection<T>, Array, List<T>, Dictionary<TKey, TValue> Class, and HashSet<T>.

In addition to representing the sequence, any class that inherits from IEnumerable<T> must provide an IEnumerator<T>. The enumerator exposes the iterator for the enumerable, and these two interconnected interfaces and ideas are the source of the saying "enumerate the enumerable".

"Enumerating the enumerable" is an important phrase. The enumerable is simply a structure for how to iterate, it does not hold any materialized objects. For example, when sorting, an enumerable may hold the criteria of the field to sort, but using .OrderBy() in itself will return an IEnumerable<T> which only knows how to sort. Using a call which will materialize the objects, as in iterate the set, is known as enumerating (for example .ToList()). The enumeration process will use the the enumerable definition of how in order to move through the series and return the relevant objects (in order, filtered, projected, etc.).

Only once the enumerable has been enumerated does it cause the materialization of the objects, which is when metrics like time complexity (how long it should take related to series size) and spacial complexity (how much space it should use related to series size) can be measured.

Creating your own class that inherits from IEnumerable<T> can be a little complicated depending on the underlying series that needs to be enumerable. In general it is best to use one of the existing generic collections. That said, it is also possible to inherit from the IEnumerable<T> interface without having a defined array as the underlying structure.

For example, using the Fibonacci series as the underlying sequence. Note that the call to Where simply builds an IEnumerable, and it is not until a call to enumerate that enumerable is made that any of the values are materialized.

void Main()

{

Fibonacci Fibo = new Fibonacci();

IEnumerable<long> quadrillionplus = Fibo.Where(i => i > 1000000000000); Console.WriteLine("Enumerable built"); Console.WriteLine(quadrillionplus.Take(2).Sum()); Console.WriteLine(quadrillionplus.Skip(2).First());

IEnumerable<long> fibMod612 = Fibo.OrderBy(i => i % 612); Console.WriteLine("Enumerable built"); Console.WriteLine(fibMod612.First());//smallest divisible by 612

}

public class Fibonacci : IEnumerable<long>

{

private int max = 90;

//Enumerator called typically from foreach public IEnumerator GetEnumerator() {

long n0 = 1; long n1 = 1;

Console.WriteLine("Enumerating the Enumerable"); for(int i = 0; i < max; i++){

yield return n0+n1; n1 += n0;

n0 = n1-n0;

GoalKicker.com – C# Notes for Professionals

366

}

}

//Enumerable called typically from linq

IEnumerator<long> IEnumerable<long>.GetEnumerator() { long n0 = 1;

long n1 = 1;

Console.WriteLine("Enumerating the Enumerable"); for(int i = 0; i < max; i++){

yield return n0+n1; n1 += n0;

n0 = n1-n0;

}

}

}

Output

Enumerable built Enumerating the Enumerable 4052739537881

Enumerating the Enumerable 4052739537881

Enumerable built Enumerating the Enumerable 14930352

The strength in the second set (the fibMod612) is that even though we made the call to order our entire set of Fibonacci numbers, since only one value was taken using .First() the time complexity was O(n) as only 1 value needed to be compared during the ordering algorithm's execution. This is because our enumerator only asked for 1 value, and so the entire enumerable did not have to be materialized. Had we used .Take(5) instead of .First() the enumerator would have asked for 5 values, and at most 5 values would need to be materialized. Compared to needing to order an entire set and then take the first 5 values, the principle of saves a lot of execution time and space.

Section 66.19: Using Range with various Linq methods

You can use the Enumerable class alongside Linq queries to convert for loops into Linq one liners.

Select Example

Opposed to doing this:

var asciiCharacters = new List<char>(); for (var x = 0; x < 256; x++)

{

asciiCharacters.Add((char)x);

}

You can do this:

var asciiCharacters = Enumerable.Range(0, 256).Select(a => (char) a);

Where Example

In this example, 100 numbers will be generated and even ones will be extracted

GoalKicker.com – C# Notes for Professionals

367

var evenNumbers = Enumerable.Range(1, 100).Where(a => a % 2 == 0);

Section 66.20: Where

Returns a subset of items which the specified predicate is true for them.

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

Method syntax

// Select all trees with name of length 3

var shortTrees = trees.Where(tree => tree.Length == 3); // Oak, Elm

Query syntax

var shortTrees = from tree in trees where tree.Length == 3 select tree; // Oak, Elm

Section 66.21: Using SelectMany instead of nested loops

Given 2 lists

var list1 = new List<string> { "a", "b", "c" };

var list2 = new List<string> { "1", "2", "3", "4" };

if you want to output all permutations you could use nested loops like

var result = new List<string>(); foreach (var s1 in list1)

foreach (var s2 in list2) result.Add($"{s1}{s2}");

Using SelectMany you can do the same operation as

var result = list1.SelectMany(x => list2.Select(y => $"{x}{y}", x, y)).ToList();

Section 66.22: Contains

MSDN:

Determines whether a sequence contains a specified element by using a specified IEqualityComparer<T>

List<int> numbers = new List<int> { 1, 2, 3, 4, 5 }; var result1 = numbers.Contains(4); // true

var result2 = numbers.Contains(8); // false

List<int> secondNumberCollection = new List<int> { 4, 5, 6, 7 };

// Note that can use the Intersect method in this case

var result3 = secondNumberCollection.Where(item => numbers.Contains(item)); // will be true only for 4,5

Using a user defined object:

GoalKicker.com – C# Notes for Professionals

368

public class Person

{

public string Name { get; set; }

}

List<Person> objects = new List<Person>

{

new Person { Name = "Nikki"}, new Person { Name = "Gilad"}, new Person { Name = "Phil"}, new Person { Name = "John"}

};

//Using the Person's Equals method - override Equals() and GetHashCode() - otherwise it //will compare by reference and result will be false

var result4 = objects.Contains(new Person { Name = "Phil" }); // true

Using the Enumerable.Contains(value, comparer) overload:

public class Compare : IEqualityComparer<Person>

{

public bool Equals(Person x, Person y)

{

return x.Name == y.Name;

}

public int GetHashCode(Person codeh)

{

return codeh.Name.GetHashCode();

}

}

var result5 = objects.Contains(new Person { Name = "Phil" }, new Compare()); // true

A smart usage of Contains would be to replace multiple if clauses to a Contains call.

So instead of doing this:

if(status == 1 || status == 3 || status == 4)

{

//Do some business operation

}

else

{

//Do something else

}

Do this:

if(new int[] {1, 3, 4 }.Contains(status)

{

//Do some business operaion

}

else

{

//Do something else

}

GoalKicker.com – C# Notes for Professionals

369