Chapter66:LINQQueries

No Comments

LINQisanacronymwhichstandsforLanguageINtegratedQuery.Itisaconceptwhichintegratesaquerylanguage by offering a consistent model for working with data across various kinds of data sources and formats; you use the samebasiccodingpatternstoqueryandtransformdatainXMLdocuments,SQLdatabases,ADO.NETDatasets,

.NETcollections,andanyotherformatforwhichaLINQproviderisavailable.

Section66.1:Chainingmethods

ManyLINQfunctionsbothoperateonanIEnumerable<TSource>andalsoreturnanIEnumerable<TResult>.The typeparametersTSourceandTResultmayormaynotrefertothesametype,dependingonthemethodin question and any functions passed to it.

Afewexamplesofthis are

publicstaticIEnumerable<TResult>Select<TSource,TResult>(

thisIEnumerable<TSource>source,

Func<TSource,TResult>selector

)

publicstaticIEnumerable<TSource>Where<TSource>(

thisIEnumerable<TSource>source,

Func<TSource,int,bool>predicate

)

publicstaticIOrderedEnumerable<TSource>OrderBy<TSource,TKey>(

thisIEnumerable<TSource>source,

Func<TSource,TKey>keySelector

)

While some method chaining may require an entire set to be worked prior to moving on, LINQ takes advantage of deferred execution by using yield returnMSDNwhich creates an Enumerable and an Enumerator behind the scenes.TheprocessofchaininginLINQisessentiallybuildinganenumerable(iterator)fortheoriginalset–which is deferred — until materialized by enumerating the enumerable.

This allows these functions to be fluently chainedwiki, where one function can act directly on the result of another. This style of code can be used to perform many sequence based operations in a single statement.

For example, it’s possible to combine SELECT, Whereand OrderByto transform, filter and sort a sequence in a single statement.

varsomeNumbers={4,3,2,1};

varprocessed=someNumbers

.Select(n=>n*2)                    //Multiplyeachnumberby2

.Where(n=>n!=6)                 //Keepalltheresults,exceptfor6

.OrderBy(n=>n);                 //Sortinascendingorder

Output:

2

4

8

LiveDemoon.NETFiddle

Any functions that both extend and return the generic IEnumerable<T>type can be used as chained clauses in a single statement. This style of fluent programming is powerful, and should be considered when creating your own extension methods.

Section66.2:First,FirstOrDefault,Last,LastOrDefault,Single, and SingleOrDefault

Allsixmethodsreturnasinglevalue ofthesequencetype,andcanbe calledwithorwithoutapredicate.

Depending on the number of elements that match the predicateor, if no predicateis supplied, the number of elementsinthesourcesequence,theybehaveasfollows:

First()

  • Returnsthefirstelementofasequence,orthefirstelementmatchingtheprovidedpredicate.
  • If the sequence contains no elements, an InvalidOperationExceptionis thrown with the message: “Sequencecontainsnoelements”.
  • If the sequence contains no elements matching the provided predicate, an InvalidOperationExceptionis thrownwiththemessage”Sequencecontainsnomatchingelement”.

Example

//Returns”a”:

new[]{“a”}.First();

//Returns”a”:

new[]{“a”,”b”}.First();

//Returns”b”:

new[]{“a”,”b”}.First(x=>x.Equals(“b”));

//Returns”ba”:

new[]{“ba”,”be”}.First(x=>x.Contains(“b”));

//ThrowsInvalidOperationException:

new[]{“ca”,”ce”}.First(x=>x.Contains(“b”));

//ThrowsInvalidOperationException:

newstring[0].First();

LiveDemoon.NETFiddle

FirstOrDefault()

  • Returnsthefirstelementofasequence,orthefirstelementmatchingtheprovidedpredicate.
  • Ifthesequencecontainsnoelements,ornoelementsmatchingtheprovidedpredicate,returnsthedefault valueofthesequencetypeusing default(T).

Example

//Returns”a”:

new[]{“a”}.FirstOrDefault();

//Returns”a”:

new[]{“a”,”b”}.FirstOrDefault();

//Returns”b”:

new[]{“a”,”b”}.FirstOrDefault(x=>x.Equals(“b”));

//Returns”ba”:

new[]{“ba”,”be”}.FirstOrDefault(x=>x.Contains(“b”));

//Returnsnull:

new[]{“ca”,”ce”}.FirstOrDefault(x=>x.Contains(“b”));

//Returnsnull:

newstring[0].FirstOrDefault();

LiveDemoon.NETFiddle

Last()

  • Returns the last element of a sequence, or the last element matching the providedpredicate.
  • If the sequence contains no elements, an InvalidOperationExceptionis thrown with the message “Sequencecontainsnoelements.”
  • If the sequence contains no elements matching the provided predicate, an InvalidOperationExceptionis thrownwiththemessage”Sequencecontainsnomatchingelement”.

Example

//Returns”a”:

new[]{“a”}.Last();

//Returns”b”:

new[]{“a”,”b”}.Last();

//Returns”a”:

new[]{“a”,”b”}.Last(x=>x.Equals(“a”));

//Returns”be”:

new[]{“ba”,”be”}.Last(x=>x.Contains(“b”));

//ThrowsInvalidOperationException:

new[]{“ca”,”ce”}.Last(x=>x.Contains(“b”));

//ThrowsInvalidOperationException:

newstring[0].Last();

LastOrDefault()

  • Returns the last element of a sequence, or the last element matching the providedpredicate.
  • Ifthesequencecontainsnoelements,ornoelementsmatchingtheprovidedpredicate,returnsthedefault valueofthesequencetypeusing default(T).

Example

//Returns”a”:

new[]{“a”}.LastOrDefault();

//Returns”b”:

new[]{“a”,”b”}.LastOrDefault();

//Returns”a”:

new[]{“a”,”b”}.LastOrDefault(x=>x.Equals(“a”));

//Returns”be”:

new[]{“ba”,”be”}.LastOrDefault(x=>x.Contains(“b”));

//Returnsnull:

new[]{“ca”,”ce”}.LastOrDefault(x=>x.Contains(“b”));

//Returnsnull:

newstring[0].LastOrDefault();

Single()

  • Ifthesequencecontainsexactlyoneelement,orexactlyoneelementmatchingtheprovidedpredicate,that element is returned.
  • Ifthesequencecontainsnoelements,ornoelementsmatchingtheprovidedpredicate,anInvalidOperationExceptionisthrownwiththemessage”Sequencecontainsnoelements”.
  • Ifthesequencecontainsmorethanoneelement,ormorethanoneelementmatchingtheprovided predicate, an InvalidOperationExceptionis thrown with the message “Sequence contains more than one element”.
  • Note:inordertoevaluatewhetherthesequencecontainsexactlyoneelement,atmosttwoelementshasto

beenumerated.

Example

//Returns”a”:

new[]{“a”}.Single();

//ThrowsInvalidOperationExceptionbecausesequencecontainsmorethanoneelement:

new[]{“a”,”b”}.Single();

//Returns”b”:

new[]{“a”,”b”}.Single(x=>x.Equals(“b”));

//ThrowsInvalidOperationException:

new[]{“a”,”b”}.Single(x=>x.Equals(“c”));

//ThrowsInvalidOperationException:

newstring[0].Single();

//ThrowsInvalidOperationExceptionbecausesequencecontainsmorethanoneelement:

new[]{“a”,”a”}.Single();

SingleOrDefault()

  • Ifthesequencecontainsexactlyoneelement,orexactlyoneelementmatchingtheprovidedpredicate,that element is returned.
  • Ifthesequencecontainsnoelements,ornoelementsmatchingtheprovidedpredicate,default(T)is returned.
  • Ifthesequencecontainsmorethanoneelement,ormorethanoneelementmatchingtheprovided predicate, an InvalidOperationExceptionis thrown with the message “Sequence contains more than one element”.
  • Ifthesequencecontainsnoelementsmatchingtheprovidedpredicate,returnsthedefaultvalueofthe sequence type using default(T).
  • Note:inordertoevaluatewhetherthesequencecontainsexactlyoneelement,atmosttwoelementshasto

beenumerated.

Example

//Returns”a”:

new[]{“a”}.SingleOrDefault();

//returns”a”

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

//Returnsnull:

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

//ThrowsInvalidOperationException:

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

//ThrowsInvalidOperationException:

new[]{“a”,”b”}.SingleOrDefault();

//Returnsnull:

newstring[0].SingleOrDefault();

Recommendations

  • Although you can use FirstOrDefault, LastOrDefaultor SingleOrDefaultto check whether a sequence containsanyitems,AnyorCountaremorereliable.Thisisbecauseareturnvalueofdefault(T)fromoneof thesethreemethodsdoesn’tprovethatthesequenceisempty,asthevalueofthefirst/last/singleelement of the sequence could equally be default(T)
  • Decideonwhichmethodsfitsyourcode’spurposethemost.Forinstance,useSingleonlyifyoumust ensurethatthereisasingleiteminthecollectionmatchingyourpredicate—otherwiseuseFirst;asSinglethrowanexceptionifthesequencehasmorethanonematchingelement.Thisofcourseappliestothe “*OrDefault”-counterparts as well.
  • Regardingefficiency:Althoughit’softenappropriatetoensurethatthereisonlyoneitem(Single)or,either only one or zero (SingleOrDefault) items, returned by a query, both of these methods require more, and oftentheentirety,ofthecollectiontobeexaminedtoensurethereinnosecondmatchtothequery.Thisis unlikethebehaviorof,forexample,theFirstmethod,whichcanbesatisfiedafterfindingthefirstmatch.

Section66.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 IEqualityCompareris used to compare the items within the two sets. There is an overloadwhichacceptsanIEqualityComparerasanargument.

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

LiveDemoon.NETFiddle

Inthiscase.Except(second)excludeselementscontainedinthearraysecond,namely2and3(0and5arenot containedinthefirstarrayandareskipped).

NotethatExceptimpliesDistinct(i.e.,itremovesrepeatedelements).Forexample:

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

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

//inThirdButNotInSecond={1,4}

Output:

1

4

LiveDemoon.NETFiddle

Inthiscase,theelements1and4arereturnedonlyonce.

Implementing IEquatableor providing the function an IEqualityComparerwill allow using a different method to comparetheelements.NotethattheGetHashCodemethodshouldalsobeoverriddensothatitwillreturnan identical hash code for objectthat are identical according to the IEquatableimplementation.

ExampleWithIEquatable:

classHoliday:IEquatable<Holiday>

{

publicstringName{get;set;}

publicboolEquals(Holidayother)

{

returnName==other.Name;

}

//GetHashCodemustreturntruewheneverEqualsreturnstrue.

publicoverrideintGetHashCode()

{

//GethashcodefortheNamefieldifitisnotnull.

returnName?.GetHashCode()??0;

}

}

publicclassProgram

{

publicstaticvoidMain()

{

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

List<Holiday>remoteHolidays=newList<Holiday>

{

newHoliday{Name=”Xmas”},

newHoliday{Name=”Hanukkah”},

newHoliday{Name=”Ramadan”}

};

List<Holiday>localHolidays=newList<Holiday>

{

newHoliday{Name=”Xmas”},

newHoliday{Name=”Ramadan”}

};

holidayDifference=remoteHolidays

.Except(localHolidays)

.ToList();

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

}

}

Output:

Hanukkah

LiveDemoon.NETFiddle

Section66.4:SelectMany

The SelectMany linq method ‘flattens’ an IEnumerable<IEnumerable<T>>into an IEnumerable<T>. All of the T elementswithintheIEnumerableinstancescontainedinthesourceIEnumerablewillbecombinedintoasingle IEnumerable.

varwords=new[]{“a,b,c”,”d,e”,”f”};

varsplitAndCombine=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.

Notethat,unlikeSELECT(),thenumberofelementsintheoutputdoesn’tneedtobethesameaswereintheinput.

Morereal-worldexample

classSchool

{

publicStudent[]Students{get;set;}

}

classStudent

{

publicstringName{get;set;}

}

varschools=new[]{

newSchool(){Students=new[]{newStudent{Name=”Bob”},newStudent{Name=”Jack”}}},

newSchool(){Students=new[]{newStudent{Name=”Jim”},newStudent{Name=”John”} }}

};

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

foreach(varstudentinallStudents)

{

Console.WriteLine(student.Name);

}

Output:

Bob

Jack

Jim

John

LiveDemoon.NETFiddle

Section66.5:Any

Anyisusedtocheckifanyelementofacollectionmatchesaconditionor not.

seealso:.All,AnyandFirstOrDefault:bestpractice

  1. Empty parameter

Any:Returnstrueifthecollectionhasanyelementsandfalseifthecollectionisempty:

varnumbers=newList<int>();

boolresult=numbers.Any();//false

varnumbers=newList<int>(){1,2,3,4,5};

boolresult=numbers.Any();//true

2. Lambdaexpressionasparameter

Any:Returnstrueifthecollectionhasoneormoreelementsthatmeettheconditioninthelambdaexpression:

vararrayOfStrings=newstring[]{“a”,”b”,”c”};

arrayOfStrings.Any(item=>item==”a”);                      //true

arrayOfStrings.Any(item=>item==”d”);                      //false

3. Empty collection

Any:Returnsfalseifthecollectionisemptyandalambdaexpressionissupplied:

varnumbers=newList<int>();

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

Note:Anywill 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.

LiveDemoon.NETFiddle

Section66.6:JOINS

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

Inner,Left,Right,CrossandFullOuterJoins.

Thefollowingtwolistsareusedintheexamplesbelow:

varfirst=newList<string>(){“a”,”b”,”c”};//Leftdata

varsecond=newList<string>(){“a”,”c”,”d”};//Rightdata

(Inner)Join

varresult=fromfinfirst

joinsinsecondon fequalss

selectnew{f,s};

varresult=first.Join(second,

f=>f,

s=>s,

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

//Result:{“a”,”a”}

//                      {“c”,”c”}

Leftouterjoin

varleftOuterJoin=fromfinfirst

joinsinsecondonfequalssintotemp

fromtintemp.DefaultIfEmpty()

selectnew{First=f,Second=t};

//Orcanalsodo:

varleftOuterJoin=fromfinfirst

fromsinsecond.Where(x=>x==f).DefaultIfEmpty()

selectnew{First=f,Second=s};

//Result:{“a”,”a”}

//                       {“b”,null}

//                       {“c”,”c”}

//Leftouterjoinmethodsyntax

varleftOuterJoinFluentSyntax=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});

RightOuterJoin

varrightOuterJoin=fromsinsecond

joinfinfirstonsequalsfintotemp

fromtintemp.DefaultIfEmpty()

selectnew{First=t,Second=s};

//Result:{“a”,”a”}

//                      {“c”,”c”}

//                      {null,”d”}

CrossJoin

varCrossJoin=fromfinfirst

fromsinsecond

selectnew{f,s};

//Result:{“a”,”a”}

//                      {“a”,”c”}

//                      {“a”,”d”}

//                      {“b”,”a”}

//                      {“b”,”c”}

//                      {“b”,”d”}

// {“c”,”a”}

// {“c”,”c”}

// {“c”,”d”}

FullOuterJoin

varfullOuterjoin=leftOuterJoin.Union(rightOuterJoin);

//Result:{“a”,”a”}

//                      {“b”,null}

//                      {“c”,”c”}

//                      {null,”d”}

Practicalexample

TheexamplesabovehaveasimpledatastructuresoyoucanfocusonunderstandingthedifferentLINQjoins technically, but in the real world you would have tables with columns you need to join.

Inthefollowingexample,thereisjustoneclassRegionused,inrealityyouwouldjointwoormoredifferenttables whichholdthesamekey(inthisexamplefirstandsecondarejoinedviathecommonkeyID).

Example:Considerthe followingdatastructure:

publicclassRegion

{

publicInt32ID;

publicstringRegionDescription;

publicRegion(Int32pRegionID,stringpRegionDescription=null)

{

ID=pRegionID;RegionDescription=pRegionDescription;

}

}

Nowpreparethedata(i.e.populatewithdata):

//Leftdata

varfirst=newList<Region>()

{newRegion(1),newRegion(3),newRegion(4)};

//Rightdata

varsecond=newList<Region>()

{

newRegion(1,”Eastern”),             newRegion(2,”Western”),

newRegion(3,”Northern”),newRegion(4,”Southern”)

};

Youcanseethatinthisexamplefirstdoesn’tcontainanyregiondescriptionssoyouwanttojointhemfrom

second.Thentheinnerjoinwouldlooklike:

//dotheinnerjoin

varresult=fromfinfirst

joinsinsecondon f.IDequalss.ID

selectnew{f.ID,s.RegionDescription};

//Result:{1,”Eastern”}

//                       {3,Northern}

//                       {4,”Southern”}

Thisresulthascreatedanonymousobjectsonthefly,whichisfine,butwehavealreadycreatedaproperclass-so wecanspecifyit:InsteadofSELECTNEW{f.ID,s.RegionDescription};wecansaySELECTNEWRegion(f.ID,s.RegionDescription);,whichwillreturnthesamedatabutwillcreateobjectsoftypeRegion-thatwillmaintain compatibility with the other objects.

Livedemoon.NETfiddle

Section66.7:SkipandTake

The Skip method returns a collection excluding a number of items from the beginning of the source collection. The number of items excluded is the number given as an argument. If there are less items in the collection than specified in the argument then an empty collection is returned.

The Take method returns a collection containing a number of elements from the beginning of the source collection. The number of items included is the number given as an argument. If there are less items in the collection than specified in the argument then the collection returned will contain the same elements as the source collection.

varvalues=new[]{5,4,3,2,1};

varskipTwo =values.Skip(2); //{3,2,1}

vartakeThree =values.Take(3); //{5,4,3}

varskipOneTakeTwo=values.Skip(1).Take(2);//{4,3}

vartakeZero                     =values.Take(0);                           //AnIEnumerable<int>with0items

LiveDemoon.NETFiddle

SkipandTakearecommonlyusedtogethertopaginateresults,forinstance:

IEnumerable<T>GetPage<T>(IEnumerable<T>collection,intpageNumber,intresultsPerPage){

intstartIndex=(pageNumber-1)*resultsPerPage;

returncollection.Skip(startIndex).Take(resultsPerPage);

}

Warning:LINQ to Entities only supports Skip on ordered queries. If you try to use Skip without ordering you will get a NotSupportedExceptionwith the message “The method ‘Skip’ is only supported for sorted input in LINQ to Entities. The method ‘OrderBy’ must be called before the method ‘Skip’.”

Section66.8:DefiningavariableinsideaLinqquery(let keyword)

Inordertodefineavariableinsidealinqexpression,youcanusethelet keyword.Thisisusuallydoneinorderto storetheresultsofintermediatesub-queries,forexample:

int[]numbers={0,1,2,3,4,5,6,7,8,9};

varaboveAverages=fromnumberinnumbers

letaverage=numbers.Average()

letnSquared=Math.Pow(number,2)

wherenSquared>average

selectnumber;

Console.WriteLine(“Theaverageofthenumbersis{0}.”,numbers.Average());

foreach(intninaboveAverages)

{

Console.WriteLine(“Queryresultincludes number{0} withsquareof {1}.”,n, Math.Pow(n,2));

}

Output:

Theaverageofthenumbersis4.5.

Query result includes number 3 with square of 9.

Query result includes number 4 with square of 16.

Query result includes number 5 with square of 25.

Query result includes number 6 with square of 36.

Query result includes number 7 with square of 49.

Query result includes number 8 with square of 64.

Queryresultincludesnumber9withsquareof81.

ViewDemo

Section66.9:Zip

The Zipextension method acts upon two collections. It pairs each element in the two series together based on position. With a Funcinstance, we use Zipto handle elements from the two C# collections in pairs. If the series differ in size, the extra elements of the larger series will be ignored.

Totakeanexamplefromthebook”C#inaNutshell”,

int[]numbers={3,5,7};

string[]words= {“three”,”five”,”seven”,”ignored”};

IEnumerable<string>zip=numbers.Zip(words,(n,w)=>n+”=”+w);

Output:

3=three

5=five

7=seven

ViewDemo

Section66.10:RangeandRepeat

TheRangeandRepeatstaticmethodsonEnumerablecanbeusedtogeneratesimplesequences.

Range

Enumerable.Range()generatesasequenceofintegersgivenastartingvalueandacount.

//Generateacollectioncontainingthenumbers1-100([1,2,3,…,98,99,100])

varrange=Enumerable.Range(1,100);

LiveDemoon.NETFiddle

Repeat

Enumerable.Repeat()generatesasequenceofrepeatingelementsgivenanelementandthenumberofrepetitions required.

//Generateacollectioncontaining”a”,threetimes([“a”,”a”,”a”])

varrepeatedValues=Enumerable.Repeat(“a”,3);

LiveDemoon.NETFiddle

Section66.11:Basics

LINQislargelybeneficialforqueryingcollections(orarrays). For example, given the following sample data:

varclassroom=newClassroom

{

new Student { Name = “Alice”, Grade = 97, HasSnack = true },
new Student { Name = “Bob”, Grade = 82, HasSnack = false },
new Student { Name = “Jimmy”, Grade = 71, HasSnack = true },
new Student { Name = “Greg”, Grade = 90, HasSnack = false },
new Student { Name = “Joe”, Grade = 59, HasSnack = false }

}

Wecan”query”onthisdatausingLINQsyntax.Forexample,toretrieveallstudentswhohaveasnacktoday:

varstudentsWithSnacks=fromsinclassroom.Students

wheres.HasSnack

selects;

Or,toretrievestudentswithagradeof90orabove,andonlyreturntheirnames,notthefullStudentobject:

vartopStudentNames=fromsinclassroom.Students

wheres.Grade>=90

selects.Name;

The LINQ feature is comprised of two syntaxes that perform the same functions, have nearly identical performance, butarewrittenverydifferently.Thesyntaxintheexampleaboveiscalledquerysyntax.Thefollowingexample, however,illustratesmethodsyntax.Thesamedatawillbereturnedasintheexampleabove,butthewaythe query is written is different.

vartopStudentNames=classroom.Students

.Where(s=>s.Grade>=90)

.Select(s=>s.Name);

Section66.12:All

Allisusedtocheck,ifallelementsofacollectionmatchaconditionornot.

seealso:.Any

  1. Empty parameter

All:isnotallowedtobeusedwithemptyparameter.

2. Lambdaexpressionasparameter

All:Returnstrueifallelementsofcollectionsatisfiesthelambdaexpressionandfalseotherwise:

varnumbers=newList<int>(){1,2,3,4,5};

boolresult=numbers.All(i=>i<10);//true

boolresult=numbers.All(i=>i>=3);//false

3. Empty collection

All:Returnstrueifthecollectionisemptyandalambdaexpressionissupplied:

varnumbers=newList<int>();

boolresult=numbers.All(i=>i>=0);//true

Note:Allwill stop iteration of the collection as soon as it finds an elementnotmatching the condition. This means that the collection will not necessarily be fully enumerated; it will only be enumerated far enough to find the firstitem notmatchingthe condition.

Section66.13:Aggregate

AggregateAppliesanaccumulatorfunctionoverasequence.

int[]intList={1,2,3,4,5,6,7,8,9,10};

intsum=intList.Aggregate((prevSum,current)=>prevSum+current);

//sum=55

  • AtthefirststepprevSum=1
  • AtthesecondprevSum=prevSum(atthefirststep)+2
  • Atthei-thstepprevSum=prevSum(atthe(i-1)step)+i-thelementofthearray

string[]stringList={“Hello”,”World”,”!”};

stringjoinedString=stringList.Aggregate((prev,current)=>prev+””+current);

//joinedString=”HelloWorld!”

A second overload of Aggregatealso receives an seedparameter which is the initial accumulator value. This can be used to calculate multiple conditions on a collection without iterating it more than once.

List<int>items=newList<int> {1,2,3,4,5,6,7,8,9,10,11,12};

Forthecollectionofitemswewanttocalculate

  1. Thetotal.Count
  2. Theamountofevennumbers
  3. Collecteachforthitem

UsingAggregateitcanbedonelikethis:

varresult=items.Aggregate(new{Total=0,Even=0,FourthItems=newList<int>()}, (accumelative,item)=>

new{

Total=accumelative.Total+1,

Even=accumelative.Even+(item%2==0?1:0),

FourthItems=(accumelative.Total+1)%4==0?

newList<int>(accumelative.FourthItems){item} : accumelative.FourthItems

});

//Result:

//Total=12

//Even=6

//FourthItems=[4,8,12]

Notethatusingananonymoustypeastheseedonehastoinstantiateanewobjecteachitembecausethepropertiesare readonly.Usingacustomclassonecansimplyassigntheinformationandnonewisneeded(onlywhengivingtheinitial seedparameter

Section66.14:Distinct

ReturnsuniquevaluesfromanIEnumerable.Uniquenessisdeterminedusingthedefaultequalitycomparer.

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

vardistinct=array.Distinct();

//distinct={1,2,3,4,5}

Tocompareacustomdatatype,weneedtoimplementtheIEquatable<T>interfaceandprovideGetHashCodeand

Equalsmethodsforthetype.Ortheequalitycomparermaybeoverridden:

classSSNEqualityComparer:IEqualityComparer<Person>{

publicboolEquals(Persona,Personb)=>returna.SSN==b.SSN;

publicintGetHashCode(Personp)=>p.SSN;

}

List<Person>people;

distinct=people.Distinct(SSNEqualityComparer);

Section 66.15: SelectMany: Flattening a sequence of sequences

varsequenceOfSequences=new[]{new[]{1,2,3},new[]{4,5},new[]{6}}; varsequence=sequenceOfSequences.SelectMany(x=>x);

//returns{1,2,3,4,5,6}

Use SelectMany()if you have, or you are creating a sequence of sequences, but you want the result as one long sequence.

InLINQQuerySyntax:

varsequence=fromsubSequenceinsequenceOfSequences

fromiteminsubSequence

selectitem;

Ifyouhaveacollectionofcollectionsandwouldliketobeabletoworkondatafromparentandchildcollectionat the same time, it is also possible with SelectMany.

Let’sdefinesimpleclasses

publicclassBlogPost

{

publicintId{get;set;}

publicstringContent{get;set;}

publicList<Comment>Comments{get;set;}

}

publicclassComment

{

publicintId{get;set;}

publicstringContent{get;set;}

}

Let’sassumewehavefollowingcollection.

List<BlogPost>posts=newList<BlogPost>()

{

newBlogPost()

{

Id=1,

Comments=newList<Comment>()

{

newComment()

{

Id=1,

Content=”It’sreallygreat!”,

},

newComment()

{

Id=2,

Content=”Coolpost!”

}

}

},

newBlogPost()

{

Id=2,

Comments=newList<Comment>()

{

newComment()

{

Id=3,

Content=”Idon’tthinkyou’reright”,

},

newComment()

{

Id=4,

Content=”Thispostisacompletenonsense”

}

}

}

};

NowwewanttoselectcommentsContentalongwithIdofBlogPostassociatedwiththiscomment.Inordertodo so, we can use appropriate SelectManyoverload.

varcommentsWithIds=posts.SelectMany(p=>p.Comments,(post,comment)=>new{PostId=post.Id, CommentContent=comment.Content});

OurcommentsWithIdslookslikethis

{

PostId=1,

CommentContent=”It’sreallygreat!”

},

{

PostId=1,

CommentContent=”Coolpost!”

}

PostId=2,

CommentContent=”Idon’tthinkyou’reright”

}

PostId=2,

CommentContent=”Thispostisacompletenonsense”

}

Section66.16:GroupBy

GroupByisaneasywaytosortaIEnumerable<T>collectionofitemsintodistinctgroups.

SimpleExample

Inthisfirstexample,weendupwithtwogroups,oddandevenitems.

List<int>iList=newList<int>() {1,2,3,4,5,6,7,8,9};

vargrouped=iList.GroupBy(x=>x%2==0);

//GroupsiListintoodd[13579]andeven[2468]items

foreach(vargroupingrouped)

{

foreach(intitemingroup)

{

Console.Write(item);//135792468             (firstoddtheneven)

}

}

MoreComplexExample

Let’stakegroupingalistofpeoplebyageasanexample.First,we’llcreateaPersonobjectwhichhastwo properties, Name and Age.

publicclassPerson

{

publicintAge{get;set;}

publicstringName{get;set;}

}

Thenwecreateoursamplelistofpeoplewithvariousnamesandages.

List<Person>people=newList<Person>();

people.Add(newPerson{Age=20,Name=”Mouse”});

people.Add(newPerson{Age=30,Name=”Neo”});

people.Add(newPerson{Age=40,Name=”Morpheus”});

people.Add(newPerson{Age=30,Name=”Trinity”});

people.Add(newPerson{Age=40,Name=”Dozer”});

people.Add(newPerson{Age=40,Name=”Smith”});

ThenwecreateaLINQquerytogroupourlistofpeoplebyage.

varquery=people.GroupBy(x=>x.Age);

Doingso,wecanseetheAgeforeachgroup,andhavealistofeachpersoninthegroup.

foreach(varresultinquery)

{

Console.WriteLine(result.Key);

foreach(varpersoninresult) Console.WriteLine(person.Name);

}

Thisresultsinthefollowingoutput:

20

Mouse

30

Neo

Trinity

40

Morpheus

Dozer Smith

Youcanplaywiththelivedemoon.NETFiddle

Section66.17:Querycollectionbytype/castelementsto type

interfaceIFoo{ }

classFoo:IFoo{ }

classBar:IFoo{}

varitem0=newFoo();

varitem1=newFoo();

varitem2=newBar();

varitem3=newBar();

varcollection=newIFoo[]{item0,item1,item2,item3};

UsingOfType

varfoos=collection.OfType<Foo>();//result:IEnumerable<Foo>withitem0anditem1

varbars=collection.OfType<Bar>();//result:IEnumerable<Bar>itemitem2anditem3

varfoosAndBars=collection.OfType<IFoo>();//result:IEnumerable<IFoo>withallfouritems

UsingWhere

varfoos=collection.Where(item=>itemisFoo);//result:IEnumerable<IFoo>withitem0anditem1

varbars=collection.Where(item=>itemisBar);//result:IEnumerable<IFoo>withitem2anditem3

UsingCast

varbars=collection.Cast<Bar>(); //throwsInvalidCastExceptiononthe1stitem

varfoos=collection.Cast<Foo>(); //throwsInvalidCastExceptiononthe3rditem

varfoosAndBars=collection.Cast<IFoo>(); //OK

Section66.18:EnumeratingtheEnumerable

TheIEnumerable<T>interfaceisthebaseinterfaceforallgenericenumeratorsandisaquintessentialpartof understanding LINQ. At its core, it represents the sequence.

Thisunderlyinginterfaceisinheritedbyallofthegenericcollections,suchasCollection<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”.

“Enumeratingtheenumerable”isanimportantphrase.Theenumerableissimplyastructureforhowtoiterate,it doesnotholdanymaterializedobjects.Forexample,whensorting,anenumerablemayholdthecriteriaofthefield tosort,butusing.OrderBy()initselfwillreturnanIEnumerable<T>whichonlyknowshowtosort.Usingacall whichwillmaterializetheobjects,asiniteratetheset,isknownasenumerating(forexample.ToList()).The enumerationprocesswillusethetheenumerabledefinitionofhowinordertomovethroughtheseriesandreturn therelevantobjects(inorder,filtered,projected,etc.).

Only once the enumerable has been enumerated does it cause the materialization of the objects, which is when metricsliketimecomplexity(howlongitshouldtakerelatedtoseriessize)andspacialcomplexity(howmuchspace 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 seriesthatneedstobeenumerable.Ingeneralitisbesttouseoneoftheexistinggenericcollections.Thatsaid,itis also possible to inherit from the IEnumerable<T> interface without having a defined array as the underlying structure.

Forexample,usingtheFibonacciseriesastheunderlyingsequence.NotethatthecalltoWheresimplybuildsan

IEnumerable,anditisnotuntilacalltoenumeratethatenumerableismadethatanyofthev aluesarematerialized.

voidMain()

{

FibonacciFibo=newFibonacci();

IEnumerable<long>quadrillionplus=Fibo.Where(i=>i>1000000000000); Console.WriteLine(“Enumerablebuilt”);

Console.WriteLine(quadrillionplus.Take(2).Sum());

Console.WriteLine(quadrillionplus.Skip(2).First());

IEnumerable<long>fibMod612=Fibo.OrderBy(i=>i%612);

Console.WriteLine(“Enumerablebuilt”);

Console.WriteLine(fibMod612.First());//smallestdivisibleby612

}

publicclassFibonacci:IEnumerable<long>

{

privateintmax=90;

//Enumeratorcalledtypicallyfromforeach

publicIEnumeratorGetEnumerator(){

longn0=1;

longn1=1;

Console.WriteLine(“EnumeratingtheEnumerable”);

for(inti=0;i<max;i++){

yieldreturnn0+n1;

n1+=n0;

n0=n1-n0;

}

}

//Enumerablecalledtypicallyfromlinq

IEnumerator<long>IEnumerable<long>.GetEnumerator(){

longn0=1;

longn1=1;

Console.WriteLine(“Enumerating the Enumerable”);

for(inti=0;i<max;i++){

yield return n0+n1;

n1+=n0;

n0=n1-n0;

}

}

}

Output

Enumerablebuilt

EnumeratingtheEnumerable

4052739537881

EnumeratingtheEnumerable

4052739537881

Enumerablebuilt

EnumeratingtheEnumerable

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.

Section66.19:UsingRangewithvariousLinqmethods

YoucanusetheEnumerableclassalongsideLinqqueriestoconvertforloopsintoLinqoneliners.

SelectExample

Opposed todoingthis:

varasciiCharacters=newList<char>();

for(varx=0;x<256;x++)

{

asciiCharacters.Add((char)x);

}

Youcandothis:

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

WhereExample

Inthisexample,100numberswillbegeneratedandevenoneswillbeextracted

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

Section66.20:Where

Returnsa subset of items which thespecified predicate is true for them.

List<string>trees=newList<string>{“Oak”,”Birch”,”Beech”,”Elm”,”Hazel”,”Maple”};

Method syntax

//Selectalltreeswithnameoflength3

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

Query syntax

varshortTrees=fromtreeintrees

wheretree.Length==3

selecttree;//Oak,Elm

Section66.21: UsingSelectMany insteadof nested loops

Given2lists

varlist1=newList<string>{“a”,”b”,”c”};

varlist2=newList<string> {“1″,”2″,”3″,”4”};

ifyouwanttooutputallpermutationsyoucouldusenestedloopslike

varresult=newList<string>();

foreach(vars1inlist1)

foreach(vars2inlist2)

result.Add($”{s1}{s2}”);

UsingSelectManyyoucandothesameoperationas

varresult=list1.SelectMany(x=>list2.Select(y=>$”{x}{y}”,x,y)).ToList();

Section66.22:Contains

MSDN:

DetermineswhetherasequencecontainsaspecifiedelementbyusingaspecifiedIEqualityComparer<T>

List<int>numbers=newList<int> {1,2,3,4,5};

varresult1=numbers.Contains(4);//true

varresult2=numbers.Contains(8);//false

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

//NotethatcanusetheIntersectmethodinthiscase

varresult3=secondNumberCollection.Where(item=>numbers.Contains(item));//willbetrueonly for4,5

Usingauserdefinedobject:

publicclassPerson

{

publicstringName{get;set;}

}

List<Person>objects=newList<Person>

{

new Person { Name = “Nikki”},

new Person{ Name =”Gilad”},

newPerson{Name=”Phil”},

newPerson{Name=”John”}

};

//UsingthePerson’sEqualsmethod-overrideEquals()andGetHashCode()-otherwiseit

//willcomparebyreferenceandresultwillbefalse

varresult4= objects.Contains(newPerson {Name =”Phil”});// true

UsingtheEnumerable.Contains(value,comparer)overload:

publicclassCompare:IEqualityComparer<Person>

{

publicboolEquals(Personx,Persony)

{

returnx.Name==y.Name;

}

publicintGetHashCode(Personcodeh)

{

returncodeh.Name.GetHashCode();

}

}

varresult5=objects.Contains(newPerson{Name=”Phil”},newCompare());//true

AsmartusageofContainswouldbetoreplacemultipleifclausestoaContainscall.

Soinsteadofdoingthis:

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

{

//Dosomebusinessoperation

}

else

{

//Dosomethingelse

}

Dothis:

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

{

//Dosomebusinessoperaion

}

else

{

//Dosomethingelse

}

Section66.23:Joiningmultiplesequences

ConsiderentitiesCustomer,PurchaseandPurchaseItemasfollows:

publicclassCustomer

{

publicstringId{get;set}//AuniqueIdthatidentifiescustomer

publicstringName          {get;set;}

}

publicclassPurchase

{

publicstringId{get;set}

publicstringCustomerId{get;set;}

publicstringDescription{get;set;}

}

publicclassPurchaseItem

{

publicstringId{get;set}

publicstringPurchaseId{get;set;}

publicstringDetail{get;set;}

}

Considerfollowingsampledataforaboveentities:

varcustomers=newList<Customer>()

{

newCustomer(){

Id=Guid.NewGuid().ToString(),

Name=”Customer1″

},

newCustomer(){

Id=Guid.NewGuid().ToString(),

Name=”Customer2″

}

};

varpurchases=newList<Purchase>()

{

newPurchase(){

Id=Guid.NewGuid().ToString(),

CustomerId=customers[0].Id,

Description=”Customer1-Purchase1″

},

newPurchase(){

Id=Guid.NewGuid().ToString(),

CustomerId=customers[0].Id,

Description=”Customer1-Purchase2″

},

newPurchase(){

Id=Guid.NewGuid().ToString(),

CustomerId=customers[1].Id,

Description=”Customer2-Purchase1″

},

newPurchase(){

Id=Guid.NewGuid().ToString(),

CustomerId=customers[1].Id, Description =

“Customer2-Purchase2”

}

};

varpurchaseItems=newList<PurchaseItem>()

{

newPurchaseItem(){

Id=Guid.NewGuid().ToString(),

PurchaseId=purchases[0].Id,Detail=”Pur

chase1-PurchaseItem1″

},

newPurchaseItem(){

Id=Guid.NewGuid().ToString(),

PurchaseId=purchases[1].Id,Detail=”Pur

chase2-PurchaseItem1″

},

newPurchaseItem(){

Id=Guid.NewGuid().ToString(),

PurchaseId=purchases[1].Id,Detail=”Pur

chase2-PurchaseItem2″

},

newPurchaseItem(){

Id=Guid.NewGuid().ToString(),

PurchaseId=purchases[3].Id,Detail=”Pur

chase3-PurchaseItem1″

}

};

Now,considerbelowlinq query:

varresult=fromcincustomers

joinpinpurchasesonc.Idequalsp.CustomerId //firstjoin

joinpiinpurchaseItemsonp.Idequalspi.PurchaseId selectnew //secondjoin

{

c.Name,p.Description,pi.Detail

};

Tooutputtheresultofabovequery:

foreach(varresultIteminresult)

{

Console.WriteLine($”{resultItem.Name},{resultItem.Description},{resultItem.Detail}”);

}

Theoutputofthequerywouldbe:

Customer1, Customer1-Purchase1, Purchase1-PurchaseItem1

Customer1, Customer1-Purchase2, Purchase2-PurchaseItem1

Customer1, Customer1-Purchase2, Purchase2-PurchaseItem2

Customer2,Customer2-Purchase2,Purchase3-PurchaseItem1

LiveDemoon.NETFiddle

Section66.24:Joiningonmultiplekeys

PropertyInfo[]stringProps=typeof(string).GetProperties();//stringproperties

PropertyInfo[]builderProps=typeof(StringBuilder).GetProperties();//stringbuilderproperties

varquery=

fromsinstringProps

joinbinbuilderProps

onnew{s.Name,s.PropertyType}equalsnew{b.Name,b.PropertyType} selectnew

{

s.Name,

s.PropertyType,

StringToken=s.MetadataToken,

StringBuilderToken=b.MetadataToken

};

Notethatanonymoustypesinabovejoinmustcontainsamepropertiessinceobjectsareconsideredequalonlyif alltheirpropertiesareequal.Otherwisequerywon’tcompile.

Section66.25:ToLookup

ToLookupreturnsadatastructurethatallowsindexing.Itisanextensionmethod.ItproducesanILookup instancethatcanbeindexedorenumeratedusingaforeach-loop.Theentriesarecombinedinto groupingsateachkey.-dotnetperls

string[]array={“one”,”two”,”three”};

//createlookupusingstringlengthaskey

varlookup=array.ToLookup(item=>item.Length);

//jointhevalueswhoselengthsare3

Console.WriteLine(string.Join(“,”,lookup[3]));

//output:one,two

AnotherExample:

int[]array={1,2,3,4,5,6,7,8};

//generatelookupforoddevennumbers(keyswillbe0and1)

varlookup=array.ToLookup(item=>item%2);

//printevennumbersafterjoining

Console.WriteLine(string.Join(“,”,lookup[0]));

//output:2,4,6,8

//printoddnumbersafterjoining

Console.WriteLine(string.Join(“,”,lookup[1]));

//output:1,3,5,7

Section66.26:SkipWhile

SkipWhile()isusedtoexcludeelementsuntilfirstnon-match(thismightbecounterintuitivetomost)

int[]list={42,42,6,6,6,42};

var result=list.SkipWhile(i=>i==42);

//Result:6,6,6,42

Section 66.27: Query Ordering – OrderBy() ThenBy() OrderByDescending()ThenByDescending()

string[]names={“mark”,”steve”,”adam”};

Ascending:

QuerySyntax

varsortedNames=

fromnameinnames

orderbynameselectnam

e;

MethodSyntax

varsortedNames=names.OrderBy(name=>name);

sortedNamescontains the names in followingorder: “adam”,”mark”,”steve”

Descending:

QuerySyntax

varsortedNames=

fromnameinnames

orderbynamedescending

selectname;

MethodSyntax

varsortedNames=names.OrderByDescending(name=>name);

sortedNamescontains the names in followingorder: “steve”,”mark”,”adam”

Orderbyseveralfields

Person[]people=

{

newPerson{FirstName=”Steve”,LastName=”Collins”,Age=30}, newPerson{FirstName=”Phil”,LastName=”Collins”,Age=28}, newPerson{FirstName=”Adam”,LastName=”Ackerman”,Age=29}, newPerson{FirstName=”Adam”,LastName=”Ackerman”,Age=15}

};

QuerySyntax

varsortedPeople=frompersoninpeople

orderbyperson.LastName,person.FirstName,person.Agedescending

selectperson;

MethodSyntax

sortedPeople=people.OrderBy(person=>person.LastName)

.ThenBy(person=>person.FirstName)

.ThenByDescending(person=>person.Age);

Result

  1. AdamAckerman29
  2. AdamAckerman15
  3. PhilCollins           28
  4. SteveCollins30

Section66.28:Sum

TheEnumerable.Sumextensionmethodcalculatesthesumofnumericvalues.

Incasethecollection’selementsarethemselvesnumbers,youcancalculatethesumdirectly.

int[]numbers=new int[] {1,4,6};

Console.WriteLine(numbers.Sum() );//outputs11

Incasethetypeoftheelementsisacomplextype,youcanusealambdaexpressiontospecifythevaluethat should be calculated:

vartotalMonthlySalary=employees.Sum(employee=>employee.MonthlySalary);

Sum extension method can calculate with the following types:

  • Int32
  • Int64
  • Single
  • Double
  • Decimal

Incaseyourcollectioncontainsnullabletypes,youcanusethenull-coalescingoperatortosetadefaultvaluefor null elements:

int?[]numbers=newint?[]{1,null,6};

Console.WriteLine(numbers.Sum(number=>number??0));//outputs7

Section66.29:GroupByoneormultiplefields

LetsassumewehavesomeFilmmodel:

publicclassFilm{

publicstringTitle{get;set;}

publicstringCategory{get;set;}

publicintYear{get;set;}

}

GroupbyCategoryproperty:

foreach(vargrpinfilms.GroupBy(f=>f.Category)){

vargroupCategory=grp.Key;

varnumberOfFilmsInCategory=grp.Count();

}

GroupbyCategoryandYear:

foreach(vargrpinfilms.GroupBy(f=>new{Category=f.Category,Year=f.Year})){ vargroupCategory=grp.Key.Category;

vargroupYear=grp.Key.Year;

varnumberOfFilmsInCategory=grp.Count();

}

Section66.30:OrderBy

Ordersacollectionbyaspecifiedvalue.

Whenthevalueisaninteger,doubleorfloatitstartswiththeminimumvalue,whichmeansthatyougetfirstthe negative values, than zero and afterwords the positive values (see Example 1).

Whenyouorderbyacharthemethodcomparestheasciivaluesofthecharstosortthecollection(seeExample2).

When you sort stringsthe OrderBy method compares them by taking a look at their CultureInfobut normaly starting with the first letter in the alphabet (a,b,c…).

Thiskindoforderiscalledascending,ifyouwantittheotherwayroundyouneeddescending(see OrderByDescending).

Example1:

int[]numbers={2,1,0,-1,-2};

IEnumerable<int>ascending=numbers.OrderBy(x=>x);

//returns{-2,-1,0,1,2}

Example2:

char[]letters={”,’!’,’?’,'[‘,'{‘,’+’,’1′,’9′,’a’,’A’,’b’,’B’,’y’,’Y’,’z’,’Z’};

IEnumerable<char>ascending=letters.OrderBy(x=>x);

//returns{‘,’!’,‘+’,’1′,’9′,’?’,’A’,’B’,’Y’,’Z’,‘[‘,‘a’,’b’,’y’,’z’,‘{‘}

Example:

classPerson

{

publicstringName{get;set; }

publicintAge{get;set;}

}

varpeople=new[]

{

newPerson{Name=”Alice”,Age=25}

newPerson{Name=”Carol”,Age=43}

};

varyoungestPerson=people.OrderBy(x=>x.Age).First();

varname=youngestPerson.Name;//Bob

Section66.31:AnyandFirst(OrDefault)-bestpractice

I won’t explain what Anyand FirstOrDefaultdoes because there are already two good example about them. See Any and First, FirstOrDefault, Last, LastOrDefault, Single, and SingleOrDefault for more information.

A pattern I often see in code which shouldbeavoidedis

if(myEnumerable.Any(t=>t.Foo==”Bob”))

{

varmyFoo=myEnumerable.First(t=>t.Foo==”Bob”);

//Dostuff

}

Itcouldbewrittenmoreefficientlylikethis

varmyFoo=myEnumerable.FirstOrDefault(t=>t.Foo==”Bob”);

if(myFoo!=null)

{

//Dostuff

}

Byusingthesecondexample,thecollectionissearchedonlyonceandgivethesameresultasthefirstone.The same idea can be applied to Single.

Section66.32:GroupBySumandCount

Let’stakeasampleclass:

publicclassTransaction

{

publicstringCategory{get;set;}

publicDateTimeDate{get;set;}

publicdecimalAmount{get;set;}

}

Now,letusconsideralistoftransactions:

vartransactions=newList<Transaction>

{

newTransaction{Category=”SavingAccount”,Amount=56,Date=DateTime.Today.AddDays(1)}, newTransaction{Category=”SavingAccount”,Amount=10,Date=DateTime.Today.AddDays(-10)

},

newTransaction{Category=”CreditCard”,Amount=15,Date=DateTime.Today.AddDays(1)}, newTransaction{Category=”CreditCard”,Amount=56,Date=DateTime.Today},

newTransaction{Category=”CurrentAccount”,Amount=100,Date=DateTime.Today.AddDays(5)

},

};

Ifyouwanttocalculatecategorywisesumofamountandcount,youcanuseGroupByasfollows:

varsummaryApproach1=transactions.GroupBy(t=>t.Category)

.Select(t=>new

{

Category=t.Key,

Count=t.Count(),

Amount=t.Sum(ta=>ta.Amount),

}).ToList();

Console.WriteLine(“– Summary: Approach 1 –“);

summaryApproach1.ForEach(

row=>Console.WriteLine($”Category:{row.Category},Amount:{row.Amount},Count:

{row.Count}”));

Alternatively,youcandothisinonestep:

varsummaryApproach2=transactions.GroupBy(t=>t.Category,(key,t)=>

{

vartransactionArray=tasTransaction[]??t.ToArray(); returnnew

{

Category=key,

Count=transactionArray.Length,

Amount=transactionArray.Sum(ta=>ta.Amount),

};

}).ToList();

Console.WriteLine(“–Summary:Approach2–“);

summaryApproach2.ForEach(

row=>Console.WriteLine($”Category:{row.Category},Amount:{row.Amount},Count:{row.Count}”));

Outputforboththeabovequerieswouldbesame:

Category: Saving Account, Amount: 66, Count: 2

Category: Credit Card, Amount: 71, Count: 2

Category:CurrentAccount,Amount:100,Count:1

LiveDemo in .NET Fiddle

Section66.33:SequenceEqual

SequenceEqualisusedtocomparetwoIEnumerable<T>sequenceswitheachother.

int[]a=newint[]{1,2,3};

int[]b=newint[]{1,2,3};

int[]c=newint[]{1,3,2};

boolreturnsTrue=a.SequenceEqual(b);

boolreturnsFalse=a.SequenceEqual(c);

Section66.34:ElementAtandElementAtOrDefault

ElementAtwillreturntheitematindexn.Ifnisnotwithintherangeoftheenumerable,throwsan

ArgumentOutOfRangeException.

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

numbers.ElementAt(2);       //3

numbers.ElementAt(10);//throwsArgumentOutOfRangeException

ElementAtOrDefaultwillreturntheitematindexn.Ifnisnotwithintherangeoftheenumerable,returnsa

default(T).

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

numbers.ElementAtOrDefault(2);       //3

numbers.ElementAtOrDefault(10);//0=default(int)

Both ElementAtand ElementAtOrDefaultare optimized for when the source is an IList<T>and normal indexing willbeusedinthosecases.

NotethatforElementAt,iftheprovidedindexisgreaterthanthesizeoftheIList<T>,thelistshould(butis technically not guaranteed to) throw an ArgumentOutOfRangeException.

Section66.35:DefaultIfEmpty

DefaultIfEmptyisusedtoreturnaDefaultElementiftheSequencecontainsnoelements.ThisElementcanbethe Default of the Type or a user defined instance of that Type. Example:

varchars=newList<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();//returnsnull;

UsageinLeftJoins:

With DefaultIfEmptythe traditional Linq Join can return a default object if no match was found. Thus acting as aSQL’s Left Join. Example:

varleftSequence=newList<int>(){99,100,5,20,102,105};

varrightSequence=newList<char>(){‘a’,’b’,’c’,’i’,’d’};

varnumbersAsChars=fromlinleftSequence

joinrinrightSequence

onlequals(int)rintoleftJoin

fromresultinleftJoin.DefaultIfEmpty(‘?’)selectnew

{

Number=l,

Character=result

};

foreach(variteminnumbersAsChars)

{

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

InthecasewhereaDefaultIfEmptyisused(withoutspecifyingadefaultvalue)andthatwillresultwillnomatching

itemsontherightsequenceonemustmakesurethattheobjectisnotnullbeforeaccessingitsproperties. OtherwiseitwillresultinaNullReferenceException.Example:

varleftSequence=newList<int> {1,2,5};

varrightSequence=newList<dynamic>()

{

new{Value=1}

, new{Value=2},

new{Value=3},

new{Value=4},

};

varnumbersAsChars=(fromlinleftSequence

joinrinrightSequence

onlequalsr.ValueintoleftJoin

fromresultinleftJoin.DefaultIfEmpty()selectnew

{

Left=l,

//5willnothaveamatchingobjectintherightsoresult

//willbeequaltonull.

//Toavoidanerroruse:

// –      C#6.0orabove-?.

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

Right=result?.Value

}).ToList();

Section66.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);

Inthisexample,thesingleargumentpassedtoToDictionaryisoftypeFunc<TSource,TKey>,whichreturnsthe key for each element.

Thisisaconcisewaytoperformthefollowingoperation:

Dictionary<int,User>usersById=newDictionary<intUser>();

foreach(Useruinusers)

{

usersById.Add(u.Id,u);

}

YoucanalsopassasecondparametertotheToDictionarymethod,whichisoftypeFunc<TSource,TElement>

andreturnstheValuetobeaddedforeachentry.

IEnumerable<User>users=GetUsers();

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

Itisalsopossibletospecifythe IComparerthatisusedtocomparekeyvalues.Thiscanbeusefulwhenthekeyisa string and you want it to match case-insensitive.

IEnumerable<User>users=GetUsers();

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

StringComparer.InvariantCultureIgnoreCase);

var user1 = usersByCaseInsenstiveName[“john”]; var

user2 = usersByCaseInsenstiveName[“JOHN”];

user1==user2;//Returnstrue

Note:theToDictionarymethodrequiresallkeystobeunique,theremustbenoduplicatekeys.Ifthereare,then an exception is thrown: ArgumentException:Anitemwiththesamekeyhasalreadybeenadded.If you have ascenariowhereyouknowthatyouwillhavemultipleelementswiththesamekey,thenyouarebetteroffusing ToLookupinstead.

Section66.37:Concat

Mergestwocollections(withoutremovingduplicates)

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

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

//ThroughEnumerablestaticclass

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

//Throughextensionmethod

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

Section66.38:BuildyourownLinqoperatorsfor 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>.

publicnamespaceMyNamespace

{

publicstaticclassLinqExtensions

{

publicstaticIEnumerable<List<T>>Batch<T>(thisIEnumerable<T>source,intbatchSize)

{

varbatch=newList<T>();

foreach(Titeminsource)

{

batch.Add(item);

if(batch.Count==batchSize)

{

yieldreturnbatch;

batch=newList<T>();

}

}

if(batch.Count>0)

yieldreturnbatch;

}

}

}

ThisexamplesplitstheitemsinanIEnumerable<T>intolistsofafixedsize,thelastlistcontainingtheremainderof theitems.Noticehowtheobjecttowhichtheextensionmethodisappliedispassedin(argumentsource)asthe initial argument using the thiskeyword. Then the yieldkeyword is used to output the next item in the output IEnumerable<T>before continuing with execution from that point (see yield keyword).

Thisexamplewouldbeusedinyourcodelikethis:

//usingMyNamespace;

varitems=newList<int> {2,3,4,5,6};

foreach(List<int>sublistinitems.Batch(3))

{

//dosomething

}

Onthefirstloop,sublistwouldbe{2,3,4}andonthesecond{5,6}. CustomLinQmethodscanbecombinedwithstandardLinQmethodstoo.e.g.:

//usingMyNamespace;

varresult=Enumerable.Range(0,13) //generatealist

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

.Batch(3)                                  //callourextensionmethod

.ToList()                                   //callotherstandardmethods

Thisquerywillreturnevennumbersgroupedinbatcheswithasizeof3:{0,2,4},{6,8,10},{12}

RememberyouneedausingMyNamespace;lineinordertobeabletoaccesstheextensionmethod.

Section66.39:Select-Transformingelements

SelectallowsyoutoapplyatransformationtoeveryelementinanydatastructureimplementingIEnumerable. Gettingthefirstcharacterofeachstringinthefollowinglist:

List<String>trees=newList<String>{“Oak”,”Birch”,”Beech”,”Elm”,”Hazel”,”Maple”};

Usingregular(lambda)syntax

//Thebelowselectstamenttransformseachelementintreeintoitsfirstcharacter.

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

foreach(Stringinitialininitials){

System.Console.WriteLine(initial);

}

Output:

O

B

B

E

H

M

LiveDemoon.NETFiddleUsingLINQQuerySyntax

initials=fromtreeintrees

selecttree.Substring(0,1);

Section66.40:OrderByDescending

Ordersacollectionbyaspecifiedvalue.

Whenthevalueisaninteger,doubleorfloatitstartswiththemaximalvalue,whichmeansthatyougetfirstthe positive values, than zero and afterwords the negative values (see Example 1).

Whenyouorderbyacharthemethodcomparestheasciivaluesofthecharstosortthecollection(seeExample2).

When you sort stringsthe OrderBy method compares them by taking a look at their CultureInfobut normaly starting with the last letter in the alphabet (z,y,x,…).

Thiskindoforderiscalleddescending,ifyouwantittheotherwayroundyouneedascending(seeOrderBy).

Example1:

int[]numbers={-2,-1,0,1,2};

IEnumerable<int>descending=numbers.OrderByDescending(x=>x);

//returns{2,1,0,-1,-2}

Example2:

char[]letters={”,’!’,’?’,'[‘,'{‘,’+’,’1′,’9′,’a’,’A’,’b’,’B’,’y’,’Y’,’z’,’Z’};

IEnumerable<char>descending=letters.OrderByDescending(x=>x);

//returns{‘{‘,‘z’,’y’,’b’,’a’,‘[‘,‘Z’,’Y’,’B’,’A’,’?’,’9′,’1′,’+’,‘!’,}

Example3:

classPerson

{

public    stringName{get;set; }

public                   intAge{get;set;}

}

varpeople=new[]

{

newPerson{Name=”Alice”,Age=25},

newPerson{Name=”Bob”,Age=21},

newPerson{Name=”Carol”,Age=43}

};

varoldestPerson=people.OrderByDescending(x=>x.Age).First();

varname=oldestPerson.Name;//Carol

Section66.41:Union

Merges twocollectionstocreatea distinctcollectionusingthedefault equalitycomparer

int[]numbers1={1,2,3};

int[]numbers2={2,3,4,5};

varallElement=numbers1.Union(numbers2);                //AllElementnowcontains1,2,3,4,5

LiveDemoon.NETFiddle

Section66.42:GroupJoinwithouterrangevariable

Customer[]customers=Customers.ToArray();

Purchase[]purchases=Purchases.ToArray();

vargroupJoinQuery=

fromcincustomers

joinpinpurchasesonc.IDequalsp.CustomerID

intocustPurchases

selectnew

{

CustName=c.Name,

custPurchases

};

Section66.43:LinqQuantifiers

Quantifier operations return a Boolean value if some or all of the elements in a sequence satisfy a condition. In this article, we will see some common LINQ to Objects scenarios where we can use these operators. There are 3 Quantifiers operations that can be used in LINQ:

All–usedtodeterminewhetheralltheelementsinasequencesatisfyacondition.Eg:

int[]array={10,20,30};

//Areallelements>=10?YES

array.All(element=>element>=10);

//Areallelements>=20?NO

array.All(element=>element>=20);

//Areallelements<40?YES

array.All(element=>element<40);

Any-usedtodeterminewhetheranyelementsinasequencesatisfyacondition.Eg:

int[]query=new int[] {2,3,4}

query.Any(n=>n==3);

Contains-usedtodeterminewhetherasequencecontainsaspecifiedelement.Eg:

//forintarray

int[]query=newint[]{1,2,3};

query.Contains(1);

//forstringarray

string[]query={“Tom”,”grey”};

query.Contains(“Tom”);

//forastring

varstringValue=”hello”;

stringValue.Contains(“h”);

Section66.44:TakeWhile

TakeWhilereturnselementsfromasequenceaslongastheconditionistrue

int[]list={1,10,40,50,44,70,4};

varresult=list.TakeWhile(item=>item<50).ToList();

//result={1,10,40}

Section66.45:Reverse

Inverts the order of the elements in a sequence.

IfthereisnoitemsthrowsaArgumentNullException:sourceisnull.

Example:

//Createanarray.

int[]array={1,2,3,4}; //Output:

//Callreverseextensionmethodonthearray. //4

varreverse=array.Reverse(); //3

//Writecontentsofarraytoscreen. //2

foreach(intvalueinreverse) //1

Console.WriteLine(value);

Livecodeexample

RemeberthatReverse()mayworkdiffrentdependingonthechainorderofyourLINQstatements.

//CreateListofchars

List<int>integerlist=newList<int>(){1,2,3,4,5,6};

//Reversingthelistthentakingthetwofirstelements

IEnumerable<int>reverseFirst=integerlist.Reverse<int>().Take(2);

//Taking2elementsandthenreversingonlythostwo

IEnumerable<int>reverseLast=integerlist.Take(2).Reverse();

//reverseFirstoutput:6,5

//reverseLastoutput:            2,1

Livecodeexample

Reverse()worksbybufferingeverythingthenwalkthroughitbackwards,whitchisnotveryefficient,butneitheris OrderBy from that perspective.

InLINQ-to-Objects,therearebufferingoperations(Reverse,OrderBy,GroupBy,etc)andnon-bufferingoperations (Where, Take, Skip, etc).

Example:Non-bufferingReverseextention

publicstaticIEnumerable<T>Reverse<T>(thisIList<T>list) {

for(inti=list.Count-1;i>=0;i–)

yieldreturnlist[i];

}

Livecodeexample

Thismethodcanencounterproblemsifumutatethelistwhileiterating.

Section66.46:CountandLongCount

CountreturnsthenumberofelementsinanIEnumerable<T>.Countalsoexposesanoptionalpredicateparameter that allows you to filter the elements you want to count.

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

intn=array.Count();//returnsthenumberofelementsinthearray

intx=array.Count(i=>i>2);//returnsthenumberofelementsinthearraygreaterthan2

LongCountworksthesamewayasCountbuthasareturntypeoflongandisusedforcountingIEnumerable<T>

sequencesthatarelongerthanint.MaxValue

int[]array=GetLargeArray();

longn=array.LongCount();//returnsthenumberofelementsinthearray

longx=array.LongCount(i=>i>100);//returnsthenumberofelementsinthearraygreaterthan 100

Section66.47:Incrementallybuildingaquery

Because LINQ uses deferredexecution, we can have a query object that doesn’t actually contain the values, but will return the values when evaluated. We can thus dynamically build the query based on our control flow, and evaluate it once we are finished:

IEnumerable<VehicleModel>BuildQuery(intvehicleType,SearchModelsearch,intstart=1,intcount

=-1){

IEnumerable<VehicleModel>query=_entities.Vehicles

.Where(x=>x.Active&&x.Type==vehicleType)

.Select(x=>newVehicleModel{

Id=v.Id,

Year=v.Year,

Class=v.Class,

Make=v.Make,

Model=v.Model,

Cylinders=v.Cylinders??0

});

Wecanconditionallyapplyfilters:

if(!search.Years.Contains(“all”,StringComparer.OrdinalIgnoreCase)) query=query.Where(v=>search.Years.Contains(v.Year));

if(!search.Makes.Contains(“all”,StringComparer.OrdinalIgnoreCase)){ query=query.Where(v=>search.Makes.Contains(v.Make));

}

if(!search.Models.Contains(“all”,StringComparer.OrdinalIgnoreCase)){ query=query.Where(v=>search.Models.Contains(v.Model));

}

if(!search.Cylinders.Equals(“all”,StringComparer.OrdinalIgnoreCase)) {

decimalminCylinders=0;

decimalmaxCylinders=0;

switch(search.Cylinders){

case”2-4″:

maxCylinders=4;

break;

case”5-6″:

minCylinders=5;

maxCylinders=6; break;

case”8″:

minCylinders=8;

maxCylinders=8; break;

case”10+”:

minCylinders = 10; break;

}

if(minCylinders>0){

query=query.Where(v=>v.Cylinders>=minCylinders);

}

if(maxCylinders>0) {

query=query.Where(v=>v.Cylinders<=maxCylinders);

}

}

Wecanaddasortordertothequerybasedonacondition:

switch(search.SortingColumn.ToLower()){

case”make_model”:

query=query.OrderBy(v=>v.Make).ThenBy(v=>v.Model); break;

case”year”:

query=query.OrderBy(v=>v.Year); break;

case”engine_size”:

query=query.OrderBy(v=>v.EngineSize).ThenBy(v=>v.Cylinders); break;

default:

query=query.OrderBy(v=>v.Year);//Thedefaultsorting.

}

Ourquerycanbedefinedtostartfromagivenpoint:

query=query.Skip(start-1);

anddefinedtoreturnaspecificnumberofrecords:

if(count>-1){

query=query.Take(count);

}

returnquery;

}

Oncewehavethequeryobject,wecanevaluatetheresultswithaforeachloop,oroneoftheLINQmethodsthat returns a set of values, such as ToListor ToArray:

SearchModelsm;

//populatethesearchmodelhere

//

List<VehicleModel>list=BuildQuery(5,sm).ToList();

Section 66.48: Select with Func<TSource, int, TResult>selector-Usetogetrankingofelements

On of the overloads of the SELECTextension methods also passes the indexof the current item in the collection being SELECTed. These are a few uses of it.

Getthe”rownumber”oftheitems

varrowNumbers=collection.OrderBy(item=>item.Property1)

.ThenBy(item=>item.Property2)

.ThenByDescending(item=>item.Property3)

.Select((item,index)=>new{Item=item,RowNumber=index})

.ToList();

Gettherankofanitemwithinitsgroup

varrankInGroup=collection.GroupBy(item=>item.Property1)

.OrderBy(group=>group.Key)

.SelectMany(group=>group.OrderBy(item=>item.Property2)

.ThenByDescending(item=>item.Property3)

.Select((item,index)=>new

{

Item=item,

RankInGroup=index

})).ToList();

Gettherankingofgroups(alsoknowninOracleas dense_rank)

varrankOfBelongingGroup=collection.GroupBy(item=>item.Property1)

.OrderBy(group=>group.Key)

.Select((group,index)=>new

{

Items=group,

Rank=index

})

.SelectMany(v=>v.Items,(s,i)=>new

{

Item=i,

DenseRank=s.Rank

}).ToList();

Fortestingthisyoucanuse:

publicclassSomeObject

{

public intProperty1{get;set;} public

intProperty2{get;set;}

publicintProperty3{get;set;}

publicoverridestringToString()

{

returnstring.Join(“,”,Property1,Property2,Property3);

}

}

Anddata:

List<SomeObject>collection=newList<SomeObject>

{

new SomeObject { Property1 = 1, Property2 = 1, Property3 = 1},
new SomeObject { Property1 = 1, Property2 = 2, Property3 = 1},
new SomeObject { Property1 = 1, Property2 = 2, Property3 = 2},
new SomeObject { Property1 = 2, Property2 = 1, Property3 = 1},
new SomeObject { Property1 = 2, Property2 = 2, Property3 = 1},
new SomeObject { Property1 = 2, Property2 = 2, Property3 = 1},
new SomeObject { Property1 = 2, Property2 = 3, Property3 = 1}

}

About us and this blog

We are a digital marketing company with a focus on helping our customers achieve great results across several key areas.

Request a free quote

We offer professional SEO services that help websites increase their organic search score drastically in order to compete for the highest rankings even when it comes to highly competitive keywords.

Subscribe to our newsletter!

More from our blog

See all posts
No Comments

Recent Posts