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

// returns "a"

new[] { "a", "b" }.SingleOrDefault(x => x == "a");

// Returns null:

new[] { "a", "b" }.SingleOrDefault(x => x == "c");

// Throws InvalidOperationException:

new[] { "a", "a" }.SingleOrDefault(x => x == "a");

//Throws InvalidOperationException: new[] { "a", "b" }.SingleOrDefault();

//Returns null:

new string[0].SingleOrDefault();

Recommendations

Although you can use FirstOrDefault, LastOrDefault or SingleOrDefault to check whether a sequence contains any items, Any or Count are more reliable. This is because a return value of default(T) from one of these three methods doesn't prove that the sequence is empty, as the value of the first / last / single element of the sequence could equally be default(T)

Decide on which methods fits your code's purpose the most. For instance, use Single only if you must ensure that there is a single item in the collection matching your predicate — otherwise use First; as Single throw an exception if the sequence has more than one matching element. This of course applies to the "*OrDefault"-counterparts as well.

Regarding e ciency: Although it's often appropriate to ensure that there is only one item (Single) or, either only one or zero (SingleOrDefault) items, returned by a query, both of these methods require more, and often the entirety, of the collection to be examined to ensure there in no second match to the query. This is unlike the behavior of, for example, the First method, which can be satisfied after finding the first match.

Section 66.3: Except

The Except method returns the set of items which are contained in the first collection but are not contained in the second. The default IEqualityComparer is used to compare the items within the two sets. There is an overload which accepts an IEqualityComparer as an argument.

Example:

int[] first = { 1, 2, 3, 4 }; int[] second = { 0, 2, 3, 5 };

IEnumerable<int> inFirstButNotInSecond = first.Except(second);

// inFirstButNotInSecond = { 1, 4 }

Output:

1

4

Live Demo on .NET Fiddle

In this case .Except(second) excludes elements contained in the array second, namely 2 and 3 (0 and 5 are not contained in the first array and are skipped).

GoalKicker.com – C# Notes for Professionals

352

Note that Except implies Distinct (i.e., it removes repeated elements). For example:

int[] third = { 1, 1, 1, 2, 3, 4 };

IEnumerable<int> inThirdButNotInSecond = third.Except(second);

// inThirdButNotInSecond = { 1, 4 }

Output:

1

4

Live Demo on .NET Fiddle

In this case, the elements 1 and 4 are returned only once.

Implementing IEquatable or providing the function an IEqualityComparer will allow using a di erent method to compare the elements. Note that the GetHashCode method should also be overridden so that it will return an identical hash code for object that are identical according to the IEquatable implementation.

Example With IEquatable:

class Holiday : IEquatable<Holiday>

{

public string Name { get; set; }

public bool Equals(Holiday other)

{

return Name == other.Name;

}

// GetHashCode must return true whenever Equals returns true. public override int GetHashCode()

{

//Get hash code for the Name field if it is not null. return Name?.GetHashCode() ?? 0;

}

}

public class Program

{

public static void Main()

{

List<Holiday> holidayDifference = new List<Holiday>();

List<Holiday> remoteHolidays = new List<Holiday>

{

new Holiday { Name = "Xmas" },

new Holiday { Name = "Hanukkah" }, new Holiday { Name = "Ramadan" }

};

List<Holiday> localHolidays = new List<Holiday>

{

new Holiday { Name = "Xmas" }, new Holiday { Name = "Ramadan" }

};

GoalKicker.com – C# Notes for Professionals

353

holidayDifference = remoteHolidays

.Except(localHolidays)

.ToList();

holidayDifference.ForEach(x => Console.WriteLine(x.Name));

}

}

Output:

Hanukkah

Live Demo on .NET Fiddle

Section 66.4: SelectMany

The SelectMany linq method 'flattens' an IEnumerable<IEnumerable<T>> into an IEnumerable<T>. All of the T elements within the IEnumerable instances contained in the source IEnumerable will be combined into a single

IEnumerable.

var words = new [] { "a,b,c", "d,e", "f" };

var splitAndCombine = words.SelectMany(x => x.Split(','));

// returns { "a", "b", "c", "d", "e", "f" }

If you use a selector function which turns input elements into sequences, the result will be the elements of those sequences returned one by one.

Note that, unlike SELECT(), the number of elements in the output doesn't need to be the same as were in the input.

More real-world example

class School

{

public Student[] Students { get; set; }

}

class Student

 

{

 

public string

Name { get; set; }

}

 

var schools = new

[] {

new School(){

Students = new [] { new Student { Name="Bob"}, new Student { Name="Jack"} }},

new School(){

Students = new [] { new Student { Name="Jim"}, new Student { Name="John"} }}

};

 

var allStudents =

schools.SelectMany(s=> s.Students);

foreach(var student in allStudents)

{

Console.WriteLine(student.Name);

}

Output:

Bob

GoalKicker.com – C# Notes for Professionals

354

Jack

Jim

John

Live Demo on .NET Fiddle

Section 66.5: Any

Any is used to check if any element of a collection matches a condition or not. see also: .All, Any and FirstOrDefault: best practice

1. Empty parameter

Any: Returns true if the collection has any elements and false if the collection is empty:

var numbers = new List<int>();

bool result = numbers.Any(); // false

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

2. Lambda expression as parameter

Any: Returns true if the collection has one or more elements that meet the condition in the lambda expression:

var arrayOfStrings = new string[] { "a", "b", "c" };

arrayOfStrings.Any(item

=>

item

==

"a");

//

true

arrayOfStrings.Any(item

=>

item

==

"d");

//

false

3. Empty collection

Any: Returns false if the collection is empty and a lambda expression is supplied:

var numbers = new List<int>();

bool result = numbers.Any(i => i >= 0); // false

Note: Any will stop iteration of the collection as soon as it finds an element matching the condition. This means that the collection will not necessarily be fully enumerated; it will only be enumerated far enough to find the first item matching the condition.

Live Demo on .NET Fiddle

Section 66.6: JOINS

Joins are used to combine di erent lists or tables holding data via a common key.

Like in SQL, the following kinds of Joins are supported in LINQ:

Inner, Left, Right, Cross and Full Outer Joins.

The following two lists are used in the examples below:

var first = new List<string>(){ "a","b","c"}; // Left data var second = new List<string>(){ "a", "c", "d"}; // Right data

GoalKicker.com – C# Notes for Professionals

355

(Inner) Join

var result = from f in first

join s in second on f equals s select new { f, s };

var result = first.Join(second, f => f, s => s,

(f, s) => new { f, s });

//Result: {"a","a"}

//{"c","c"}

Left outer join

var leftOuterJoin = from f in first

join s in second on f equals s into temp from t in temp.DefaultIfEmpty()

select new { First = f, Second = t};

// Or can also do:

var leftOuterJoin = from f in first

from s in second.Where(x => x == f).DefaultIfEmpty() select new { First = f, Second = s};

//Result: {"a","a"}

//{"b", null}

//{"c","c"}

//Left outer join method syntax

var leftOuterJoinFluentSyntax = first.GroupJoin(second, f => f,

s => s,

(f, s) => new { First = f, Second = s })

.SelectMany(temp => temp.Second.DefaultIfEmpty(), (f, s) => new { First = f.First, Second = s });

Right Outer Join

var rightOuterJoin = from s

in second

 

join f

in first on s equals f into temp

 

from t

in temp.DefaultIfEmpty()

 

select

new {First=t,Second=s};

// Result: {"a","a"}

 

//

{"c","c"}

 

//

{null,"d"}

 

 

 

 

Cross Join

var CrossJoin = from f in first from s in second select new { f, s };

//Result: {"a","a"}

//{"a","c"}

//{"a","d"}

//{"b","a"}

//{"b","c"}

//{"b","d"}

GoalKicker.com – C# Notes for Professionals

356

//{"c","a"}

//{"c","c"}

//{"c","d"}

Full Outer Join

var fullOuterjoin = leftOuterJoin.Union(rightOuterJoin);

//Result: {"a","a"}

//{"b", null}

//{"c","c"}

//{null,"d"}

Practical example

The examples above have a simple data structure so you can focus on understanding the di erent LINQ joins technically, but in the real world you would have tables with columns you need to join.

In the following example, there is just one class Region used, in reality you would join two or more di erent tables which hold the same key (in this example first and second are joined via the common key ID).

Example: Consider the following data structure:

public class Region

{

public Int32 ID;

public string RegionDescription;

public Region(Int32 pRegionID, string pRegionDescription=null)

{

ID = pRegionID; RegionDescription = pRegionDescription;

}

}

Now prepare the data (i.e. populate with data):

// Left data

var first = new List<Region>()

{ new Region(1), new Region(3), new Region(4) };

// Right data

var second = new List<Region>()

{

new Region(1, "Eastern"), new Region(2, "Western"), new Region(3, "Northern"), new Region(4, "Southern")

};

You can see that in this example first doesn't contain any region descriptions so you want to join them from second. Then the inner join would look like:

// do the inner join

var result = from f in first

join s in second on f.ID equals s.ID select new { f.ID, s.RegionDescription };

//Result: {1,"Eastern"}

//{3, Northern}

//{4,"Southern"}

GoalKicker.com – C# Notes for Professionals

357