Aggiornamenti

29/3/2013

In seguito ad alcune considerazioni fatte, stimolate anche da i commenti al post, ho fatto i seguenti aggiornamenti:

  • Rinominato gli extension methods in Next(...), NextOrDefault(...), Previous(...), PreviousOrDefault(...) nome più corretto rispetto alla funzionalità implementata
  • Riscritto i metodi utilizzando solo estensioni LinQ, in particolare le estensioni SkipWhile(...) e TakeWhile(...)

Problema

Ho avuto necessità di cercare un elemento successivo o precedente in una lista e/o che soddisfa una determinata condizione a partire da un elemento corrente. Così ho scritto una serie di extension methods che estendono i classici First(…), Last(…), FirstOrDefault(…) e LastOrDefault(…).

Implementazione

Code Snippet
  1. public static class LinqExtensions
  2. {
  3.     public static T Next<T>(this IEnumerable<T> source, T current)
  4.     {
  5.         return source.SkipWhile(item => !Equals(item, current)).Skip(1).First();
  6.     }
  7.  
  8.     public static T Next<T>(this IEnumerable<T> source, T current, Func<T, bool> predicate)
  9.     {
  10.         return source.SkipWhile(item => !Equals(item, current)).Skip(1).First(predicate);
  11.     }
  12.  
  13.     public static T Previous<T>(this IEnumerable<T> source, T current)
  14.     {
  15.         return source.TakeWhile(item => !Equals(item, current)).Last();
  16.     }
  17.  
  18.     public static T Previous<T>(this IEnumerable<T> source, T current, Func<T, bool> predicate)
  19.     {
  20.         return source.TakeWhile(item => !Equals(item, current)).Last(predicate);
  21.     }
  22.  
  23.     public static T NextOrDefault<T>(this IEnumerable<T> source, T current)
  24.     {
  25.         return source.SkipWhile(item => !Equals(item, current)).Skip(1).FirstOrDefault();
  26.     }
  27.  
  28.     public static T NextOrDefault<T>(this IEnumerable<T> source, T current, Func<T, bool> predicate)
  29.     {
  30.         return source.SkipWhile(item => !Equals(item, current)).Skip(1).FirstOrDefault(predicate);
  31.     }
  32.  
  33.     public static T PreviousOrDefault<T>(this IEnumerable<T> source, T current)
  34.     {
  35.         return source.TakeWhile(item => !Equals(item, current)).LastOrDefault();
  36.     }
  37.  
  38.     public static T PreviousOrDefault<T>(this IEnumerable<T> source, T current, Func<T, bool> predicate)
  39.     {
  40.         return source.TakeWhile(item => !Equals(item, current)).LastOrDefault(predicate);
  41.     }
  42. }

Test

Code Snippet
  1. [TestFixture]
  2. public class NextPreviousLinqExtensionsTest
  3. {
  4.     [Test]
  5.     public void WhenSourceIsNullArgumentNullExceptionIsThrown()
  6.     {
  7.         IEnumerable<object> enumerable = null;
  8.  
  9.         Assert.Catch<ArgumentNullException>(() => enumerable.Next(null));
  10.         Assert.Catch<ArgumentNullException>(() => enumerable.Next(null, null));
  11.         Assert.Catch<ArgumentNullException>(() => enumerable.Previous(null));
  12.         Assert.Catch<ArgumentNullException>(() => enumerable.Previous(null, null));
  13.         Assert.Catch<ArgumentNullException>(() => enumerable.NextOrDefault(null));
  14.         Assert.Catch<ArgumentNullException>(() => enumerable.NextOrDefault(null, null));
  15.         Assert.Catch<ArgumentNullException>(() => enumerable.PreviousOrDefault(null));
  16.         Assert.Catch<ArgumentNullException>(() => enumerable.PreviousOrDefault(null, null));
  17.     }
  18.  
  19.     [Test]
  20.     public void WhenPredicateIsNullArgumentNullExceptionIsThrown()
  21.     {
  22.         var list = new List<object>();
  23.  
  24.         Assert.Catch<ArgumentNullException>(() => list.Next(null, null));
  25.         Assert.Catch<ArgumentNullException>(() => list.Previous(null, null));
  26.         Assert.Catch<ArgumentNullException>(() => list.NextOrDefault(null, null));
  27.         Assert.Catch<ArgumentNullException>(() => list.PreviousOrDefault(null, null));
  28.     }
  29.  
  30.     [Test]
  31.     public void WhenCurrentElementDoesNotExistsInTheListInvalidOperationExceptionIsThrown()
  32.     {
  33.         var list = new List<object>();
  34.  
  35.         Assert.Catch<InvalidOperationException>(() => list.Next(null));
  36.         Assert.Catch<InvalidOperationException>(() => list.Next(null, item => true));
  37.         Assert.Catch<InvalidOperationException>(() => list.Previous(null));
  38.         Assert.Catch<InvalidOperationException>(() => list.Previous(null, item => true));
  39.     }
  40.  
  41.     [Test]
  42.     public void WhenCurrentElementDoesNotExistsInTheListDefualtValueIsReturned()
  43.     {
  44.         var list = new List<object>();
  45.  
  46.         Assert.IsNull(list.NextOrDefault(null));
  47.         Assert.IsNull(list.NextOrDefault(null, item => true));
  48.         Assert.IsNull(list.PreviousOrDefault(null));
  49.         Assert.IsNull(list.PreviousOrDefault(null, item => true));
  50.     }
  51.  
  52.     [Test]
  53.     public void FirstFromCurrentInValueTypeListReturnNextCorrectElement()
  54.     {
  55.         var numbers = new List<int>();
  56.  
  57.         numbers.Add(1);
  58.         numbers.Add(2);
  59.         numbers.Add(3);
  60.         numbers.Add(4);
  61.  
  62.         Assert.AreEqual(3, numbers.Next(2));
  63.         Assert.AreEqual(4, numbers.Next(2, item => item > 3));
  64.         Assert.AreEqual(3, numbers.NextOrDefault(2));
  65.         Assert.AreEqual(4, numbers.NextOrDefault(2, item => item > 3));
  66.         Assert.AreEqual(default(int), numbers.NextOrDefault(4));
  67.         Assert.AreEqual(default(int), numbers.NextOrDefault(2, item => item > 4));
  68.     }
  69.  
  70.     [Test]
  71.     public void FirstFromCurrentInReferenceTypeListReturnNextCorrectElement()
  72.     {
  73.         var objects = new List<object>();
  74.  
  75.         var objA = new object();
  76.         var objB = new object();
  77.         var objC = new object();
  78.         var objD = new object();
  79.  
  80.         objects.Add(objA);
  81.         objects.Add(objB);
  82.         objects.Add(objC);
  83.         objects.Add(objD);
  84.  
  85.         Assert.AreEqual(objC, objects.Next(objB));
  86.         Assert.AreEqual(objD, objects.Next(objB, item => item != objC));
  87.         Assert.AreEqual(objC, objects.NextOrDefault(objB));
  88.         Assert.AreEqual(objD, objects.NextOrDefault(objB, item => item != objC));
  89.         Assert.IsNull(objects.NextOrDefault(objD));
  90.         Assert.IsNull(objects.NextOrDefault(objB, item => item != objC && item != objD));
  91.     }
  92.  
  93.     [Test]
  94.     public void LastFromCurrentInValueTypeListReturnPreviousCorrectElement()
  95.     {
  96.         var numbers = new List<int>();
  97.  
  98.         numbers.Add(1);
  99.         numbers.Add(2);
  100.         numbers.Add(3);
  101.         numbers.Add(4);
  102.  
  103.         Assert.AreEqual(2, numbers.Previous(3));
  104.         Assert.AreEqual(1, numbers.Previous(3, item => item < 2));
  105.         Assert.AreEqual(2, numbers.PreviousOrDefault(3));
  106.         Assert.AreEqual(1, numbers.PreviousOrDefault(3, item => item < 2));
  107.         Assert.AreEqual(default(int), numbers.PreviousOrDefault(1));
  108.         Assert.AreEqual(default(int), numbers.PreviousOrDefault(3, item => item < 1));
  109.     }
  110.  
  111.     [Test]
  112.     public void LastFromCurrentInReferenceTypeListReturnPreviousCorrectElement()
  113.     {
  114.         var objects = new List<object>();
  115.  
  116.         var objA = new object();
  117.         var objB = new object();
  118.         var objC = new object();
  119.         var objD = new object();
  120.  
  121.         objects.Add(objA);
  122.         objects.Add(objB);
  123.         objects.Add(objC);
  124.         objects.Add(objD);
  125.  
  126.         Assert.AreEqual(objB, objects.Previous(objC));
  127.         Assert.AreEqual(objA, objects.Previous(objC, item => item != objB));
  128.         Assert.AreEqual(objB, objects.PreviousOrDefault(objC));
  129.         Assert.AreEqual(objA, objects.PreviousOrDefault(objC, item => item != objB));
  130.         Assert.IsNull(objects.PreviousOrDefault(objA));
  131.         Assert.IsNull(objects.PreviousOrDefault(objC, item => item != objB && item != objA));
  132.     }
  133. }