📜 ⬆️ ⬇️

Difficult about simple or particular Linq to objects

LINQ to objects is now firmly established in our life, with victorious steps, stepping across the entire stack of .net applications. In this article I would like to give examples of several not obvious things that happened to deal with working with LINQ. Interesting - I ask under the cat.

1. Feature of working with List <T>


LINQ differs in its implementation when an object of type IEnumerable <T> comes to it and when List <T> comes. Even in the simple Where method, different iterators are created:
if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate); if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate); if (source is List <TSource>) return new WhereListIterator<TSource>(( List <TSource>)source, predicate); * This source code was highlighted with Source Code Highlighter .
  1. if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate); if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate); if (source is List <TSource>) return new WhereListIterator<TSource>(( List <TSource>)source, predicate); * This source code was highlighted with Source Code Highlighter .
  2. if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate); if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate); if (source is List <TSource>) return new WhereListIterator<TSource>(( List <TSource>)source, predicate); * This source code was highlighted with Source Code Highlighter .
  3. if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate); if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate); if (source is List <TSource>) return new WhereListIterator<TSource>(( List <TSource>)source, predicate); * This source code was highlighted with Source Code Highlighter .
if (source is Iterator<TSource>) return ((Iterator<TSource>)source).Where(predicate); if (source is TSource[]) return new WhereArrayIterator<TSource>((TSource[])source, predicate); if (source is List <TSource>) return new WhereListIterator<TSource>(( List <TSource>)source, predicate); * This source code was highlighted with Source Code Highlighter .

By the way, note that the array will also have its own iterator, the difference of which lies in the fact that it will receive the elements directly through the call by index.

Why is this feature interesting to us? Well, for example, I was usually skeptical about calling the Last construct, because she must go through the whole enumerator to the end. However, if you apply it to an object that implements the IList <T> interface, then the last element will be obtained through the indexer, which of course is not so bad.
  1. public static TSource LastOrDefault <TSource> ( this IEnumerable <TSource> source) {
  2. if (source == null ) throw Error.ArgumentNull ( "source" );
  3. IList <TSource> list = source as IList <TSource>;
  4. if (list! = null ) {
  5. int count = list.Count;
  6. if (count> 0) return list [count - 1];
  7. }
  8. else {
* This source code was highlighted with Source Code Highlighter .

Similar optimization has been done for Single methods.
  1. public static TSource Single <TSource> ( this IEnumerable <TSource> source) {
  2. if (source == null ) throw Error.ArgumentNull ( "source" );
  3. IList <TSource> list = source as IList <TSource>;
  4. if (list! = null ) {
  5. switch (list.Count) {
  6. case 0: throw Error. NoElements ();
  7. case 1: return list [0];
* This source code was highlighted with Source Code Highlighter .


2. GroupBy Work


Suppose we have this code:
  1. class Key
  2. {
  3. private readonly int _number;
  4. public static int HashCallsCount;
  5. public static int EqualsCallsCount;
  6. public Key ( int number)
  7. {
  8. _number = number;
  9. }
  10. public bool Equals (Key other)
  11. {
  12. if (ReferenceEquals ( null , other))
  13. {
  14. return false ;
  15. }
  16. if (ReferenceEquals ( this , other))
  17. {
  18. return true ;
  19. }
  20. return other._number == _number;
  21. }
  22. public override bool Equals ( object obj)
  23. {
  24. EqualsCallsCount ++;
  25. if (ReferenceEquals ( null , obj))
  26. {
  27. return false ;
  28. }
  29. if (ReferenceEquals ( this , obj))
  30. {
  31. return true ;
  32. }
  33. if (obj.GetType ()! = typeof (Key))
  34. {
  35. return false ;
  36. }
  37. return Equals ((Key) obj);
  38. }
  39. public override int GetHashCode ()
  40. {
  41. HashCallsCount ++;
  42. return _number;
  43. }
  44. }
  45. class Test
  46. {
  47. public int Number { get ; set ; }
  48. public string Name { get ; set ; }
  49. public key key
  50. {
  51. get { return new Key (Number);}
  52. }
  53. public override string ToString ()
  54. {
  55. return string .Format ( "Number: {0}, Name: {1}" , Number, Name);
  56. }
  57. }
  58. internal class Program
  59. {
  60. private static void Main ( string [] args)
  61. {
  62. var items = Enumerable.Range (1, 20) .Select (x => new Test {Number = x% 3});
  63. foreach ( var group in items.GroupBy (x => x.Key))
  64. {
  65. Console .WriteLine ( "Group key: {0}" , group.Key);
  66. foreach ( var item in group)
  67. {
  68. Console .WriteLine (item);
  69. }
  70. }
  71. Console .WriteLine ( "EqualsCallsCount {0}" , Key.EqualsCallsCount);
  72. Console .WriteLine ( "HashCallsCount {0}" , Key.HashCallsCount);
  73. }
  74. }
* This source code was highlighted with Source Code Highlighter .

What value will be displayed for the EqualsCallsCount and HashCallsCount variables ? 17 and 20 respectively. 17 - because the values ​​that have become group keys with other keys will not be compared (just in case, I will point out that the group in this example is exactly 3). 20 is a challenge, because The hashcode from the Key object is used to determine in which group the Test object should be placed. Here it should be noted that if the hashcode ceases to give unique values ​​(for example, it will always return 0), then the number of Equals challenge will increase to 38. The reasons, I think, are understandable. Another interesting detail is that the default array of groups is 7
  1. Lookup (IEqualityComparer <TKey> comparer) {
  2. if (comparer == null ) comparer = EqualityComparer <TKey> .Default;
  3. this .comparer = comparer;
  4. groupings = new Grouping [7];
  5. }
* This source code was highlighted with Source Code Highlighter .

And then it increases linearly, copying the entire array at this step.
  1. void Resize () {
  2. int newSize = checked (count * 2 + 1);
  3. Grouping [] newGroupings = new Grouping [newSize];
  4. Grouping g = lastGrouping;
  5. do {
  6. g = g.next;
  7. int index = g.hashCode% newSize;
  8. g.hashNext = newGroupings [index];
  9. newGroupings [index] = g;
  10. } while (g! = lastGrouping);
  11. groupings = newGroupings;
  12. }
* This source code was highlighted with Source Code Highlighter .

Because you cannot specify the capacity for the GroupBy method, it is better to refrain from grouping with a large number of keys, otherwise memory problems may arise (well, of course, the speed will drop).
')

3. Enumerator is also an object.


Consider two functions:
  1. private static IEnumerable <Test> SortTest (Func < IEnumerable <Test >> list)
  2. {
  3. foreach ( var item in list (). OrderBy (x => x.Number) .ThenBy (x => x.Name))
  4. {
  5. yield return item;
  6. }
  7. }
* This source code was highlighted with Source Code Highlighter .

and
  1. private static IEnumerable <Test> SortTest2 (Func < IEnumerable <Test >> list)
  2. {
  3. return list (). OrderBy (x => x.Number) .ThenBy (x => x.Name);
  4. }
* This source code was highlighted with Source Code Highlighter .

We assume that list () returns a stable list of elements, such as an array. Are the functions equivalent? Of course not (by the way, in the last Resharper there is a corresponding bug ). In the first case, the list () function will be called whenever we pass the returned enumerator. And accordingly, if it begins to return other values, then the enumerator values ​​will become different. In the second case, the list () function will be called only once, its result will be saved in the source field of the OrderedEnumerable <TElement, TKey> class and in the future, no matter how much the enumerator returned, the values ​​in it will be the same.

Prisoners


I hope this small article will be useful for someone and will help to avoid mistakes when working with Linq to object. Comments and additions are welcome.

Links


MSDN o LINQ

Thanks for attention.

Source: https://habr.com/ru/post/124231/


All Articles