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.
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.
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
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
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
Anyisusedtocheckifanyelementofacollectionmatchesaconditionor not.
seealso:.All,AnyandFirstOrDefault:bestpractice
- 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
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
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
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
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
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);
Allisusedtocheck,ifallelementsofacollectionmatchaconditionornot.
seealso:.Any
- 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.
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
- Thetotal.Count
- Theamountofevennumbers
- 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
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”
}
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);
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();
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″
},
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.
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
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
- AdamAckerman29
- AdamAckerman15
- PhilCollins 28
- SteveCollins30
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();
}
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
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.
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();
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.
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
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
};
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”);
TakeWhilereturnselementsfromasequenceaslongastheconditionistrue
int[]list={1,10,40,50,44,70,4};
varresult=list.TakeWhile(item=>item<50).ToList();
//result={1,10,40}
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 postsRecent Posts
- Chapter97:FileandStreamI/O 27/04/2024
- Chapter96:Delegates 26/04/2024
- Chapter95:Attributes 25/04/2024