#1,199 – Equality and Inequality with Nullable Types

The equality and inequality operators work with nullable types as follows:

  • If both operands are non-null, the operators are applied to the values
  • If one operand is null and the other has a value, == returns false, != returns true
  • If both operands are null, == returns true and != returns false
            int? val1 = 5;
            int? val2 = 10;
            int? val3 = null;
            int? val4 = null;
            int? val5 = 10;

            bool b1 = val1 == val2;   // False
            b1 = val2 == val5;        // True
            b1 = val1 == val3;        // False
            b1 = val1 != val3;        // True
            b1 = val3 == val4;        // True
            b1 = val3 != val4;        // False
Advertisement

#1,198 – Using Operators with Nullable Types

When using a nullable type, you can use the operators with the nullable type that are associated with the underlying value type.

The example below shows how we can use the < and > operators with the int? type.  They work as they would work with the int type itself, for non-null values.

            int? val1 = 5;
            int? val2 = 10;

            bool b1 = val1 < val2;   // True

If one of the two values is null, the expression will evaluate to false, regardless of the other value.

            val2 = null;
            b1 = val1 < val2;    // False
            b1 = val2 < val1;    // False
            b1 = val1 == val2;   // False

#1,197 – Iterator Can Generate Enumerable of Infinite Length

An iterator can be written to generate an enumerable sequence of infinite length.  The iterator will execute for as long as client code continues to request the next element in the sequence.

Below is a simple iterator that generates prime numbers.  Note that we can still use the iterator, although its implementation includes an infinite loop.  The loop will execute only as many times as needed.

        static void Main(string[] args)
        {
            Console.Write("Enter limit:");
            int limit = int.Parse(Console.ReadLine());

            foreach (int p in AllPrimes())
            {
                if (p > limit)
                    break;
                Console.WriteLine(p);
            }

            Console.ReadLine();
        }

        private static IEnumerable<int> AllPrimes()
        {
            List<int> primesSoFar = new List<int>();
            primesSoFar.Add(2);
            yield return 2;
            primesSoFar.Add(3);
            yield return 3;

            int testPrime = 5;
            while (true)
            {
                bool isPrime = true;
                foreach (int n in primesSoFar)
                {
                    if (testPrime % n == 0)
                    {
                        isPrime = false;
                        break;
                    }
                }

                if (isPrime)
                {
                    primesSoFar.Add(testPrime);
                    yield return testPrime;
                }

                testPrime += 2;
            }
        }

1197-001

#1,196 – Using Fluent-Style Syntax when Chaining Iterators

You can chain iterator code together using a fluent-style syntax if you define extension methods for the corresponding IEnumerable<T> type that you’re using.  In the code below, we chain several iterators together, progressively filtering an IEnumerable<Dog> collection.

    class Program
    {
        static void Main(string[] args)
        {
            foreach (Dog d in AllMyDogs().YoungDogs().HerdingDogs())
            {
                Console.WriteLine(d);
                if (d.Breed == Breed.JackRussell)
                    break;
            }

            Console.ReadLine();
        }

        private static IEnumerable<Dog> AllMyDogs()
        {
            yield return new Dog("Kirby", Breed.BorderCollie, 14);
            yield return new Dog("Jack", Breed.JackRussell, 15);
            yield return new Dog("Ruby", Breed.Mutt, 4);
            yield return new Dog("Lassie", Breed.Collie, 19);
            yield return new Dog("Shep", Breed.Collie, 2);
            yield return new Dog("Foofoo", Breed.Sheltie, 8);
            yield return new Dog("Pongo", Breed.Dalmatian, 4);
            yield return new Dog("Rooster", Breed.WestHighlandTerrier, 1);
        }
    }

    static class DogFilters
    {
        public static IEnumerable<Dog> YoungDogs(this IEnumerable<Dog> dogs)
        {
            foreach (Dog d in dogs)
                if (d.Age < 10)
                    yield return d;
        }

        public static IEnumerable<Dog> HerdingDogs(this IEnumerable<Dog> dogs)
        {
            foreach (Dog d in dogs)
                if ((d.Breed == Breed.BorderCollie) ||
                    (d.Breed == Breed.Collie) ||
                    (d.Breed == Breed.Sheltie))
                    yield return d;
        }
    }

Here’s the output:
1196-001

#1,195 – Iterator Produces Only as Many Elements as Are Needed

When implementing an iterator, you write code that generates a sequence of elements using the yield return statement to return each consecutive element.

The full body of the iterator code may not execute.  Only the code required to return the elements iterated upon is executed.  In the example below, we iterate over a sequence produced by an iterator, using foreach.

Executing this code, only a portion of the AllMyDogs() method is executed, since we exit early out of  the foreach loop.

            static void Main(string[] args)
            {
                foreach (Dog d in AllMyDogs())
                {
                    Console.WriteLine(d);
                    if (d.Breed == Breed.JackRussell)
                        break;
                }

                Console.ReadLine();
            }

            private static IEnumerable<Dog> AllMyDogs()
            {
                Console.WriteLine("* returning Kirby");
                yield return new Dog("Kirby", Breed.BorderCollie, 14);
                Console.WriteLine("* returning Jack");
                yield return new Dog("Jack", Breed.JackRussell, 15);
                Console.WriteLine("* returning Ruby");
                yield return new Dog("Ruby", Breed.Mutt, 4);
                Console.WriteLine("* returning Lassie");
                yield return new Dog("Lassie", Breed.Collie, 12);
                Console.WriteLine("* returning Foofoo");
                yield return new Dog("Foofoo", Breed.Sheltie, 8);
            }

1195-001

#1,194 – Chaining Iterators Together

An iterator can work on an IEnumerable<T>, using one sequence as input and generating a second sequence from the first.

In the example below, we use the output of one iterator as the input for another iterator.

        static void Main(string[] args)
        {
            IEnumerable<Dog> dogs1 = AllMyDogs();

            Console.WriteLine("============");
            foreach (Dog d in dogs1)
                Console.WriteLine(d);

            IEnumerable<Dog> dogs2 = YoungDogs(dogs1);

            Console.WriteLine("============");
            foreach (Dog d in dogs2)
                Console.WriteLine(d);

            IEnumerable<Breed> breeds = BreedsByNamePattern(dogs2, "oo");

            Console.WriteLine("============");
            foreach (Breed b in breeds)
                Console.WriteLine(b);

            Console.ReadLine();
        }

        private static IEnumerable<Dog> AllMyDogs()
        {
            yield return new Dog("Kirby", Breed.BorderCollie, 14);
            yield return new Dog("Jack", Breed.JackRussell, 15);
            yield return new Dog("Ruby", Breed.Mutt, 4);
            yield return new Dog("Lassie", Breed.Collie, 12);
            yield return new Dog("Foofoo", Breed.Sheltie, 8);
            yield return new Dog("Pongo", Breed.Dalmatian, 4);
            yield return new Dog("Rooster", Breed.WestHighlandTerrier, 1);
        }

        private static IEnumerable<Dog> YoungDogs(IEnumerable<Dog> dogs)
        {
            foreach (Dog d in dogs)
                if (d.Age < 10)
                    yield return d;
        }

        private static IEnumerable<Breed> BreedsByNamePattern(IEnumerable<Dog> dogs, string pattern)
        {
            foreach (Dog d in dogs)
                if (d.Name.Contains(pattern))
                    yield return d.Breed;
        }

1194-001

#1,193 – yield Statement and try/catch Blocks

The yield return statement is used when defining an iterator to generate the next element within a sequence (IEnumerable).  You cannot include a yield return statement in any of the following places:

  • within a try block that has a catch clause
  • within a catch block
  • within a finally block

You can include a yield return statement within a try block that only has a finally block.

        private static IEnumerable<int> IntsAndTheirDoubles()
        {
            for (int i = 1; i <= 5; i++ )
            {
                yield return i;
                try
                {
                    // Error: Cannot yield a value in the body of a try block
                    //   with a catch clause
                    yield return 2 * i;
                }
                catch (Exception xx)
                {
                    Console.WriteLine("Uh-oh");
                }
            }
        }

#1,192 – Following the TryParse Pattern

The int.TryParse method does the same thing as the int.Parse method, but without throwing an exception.  Instead, it returns a boolean value indicating whether the method succeeded or not and writes the result to an out parameter.

You might follow the same pattern when writing your own code, providing a method that throws an exception on failure and a TryXxx version of the method that returns a boolean indicating whether the method succeeded.  Below is an example.

    public class Dog
    {
        public string Name { get; set; }
        public int Age { get; set; }

        public Dog(string name, int age)
        {
            Name = name;
            Age = age;
        }

        public bool TryBark(out string barkSound)
        {
            bool success = false;
            barkSound = "";

            if (Age <= 10)
            {
                success = true;
                barkSound = "Woof";
            }

            return success;
        }

        public string Bark()
        {
            string barkSound;

            if (!TryBark(out barkSound))
                throw new Exception("This dog can't bark");
            return barkSound;
        }
    }

#1,191 – Lambda Can’t Capture ref or out Parameters

Lambda expressions can make use of variables declared in a containing scope, i.e. outside of the expression itself.  They cannot, however, use variables that are defined as ref or out parameters in an outer scope.

In the example below, it’s a compile-time error to include the valOut parameter in the lambda expression.

        static void SomeMethod(int valIn, out int valOut)
        {
            int local;

            Action doCalc = () =>
            {
                local = valIn * 2;   // this is ok
                valOut = valIn * i;  // this is not--compile-time error
            };
        }

As an alternative, you can assign a value returned by the lambda to an out parameter.

        static void SomeMethod(int valIn, out int valOut)
        {
            // Ok to assign result of lambda to
            // out parameter
            Func<int> doCalc2 = () => valIn * 2;
            valOut = doCalc2();   // Allowed
        }

#1,190 – A Lambda Expression Can Be Recursive

A lambda expression can be recursive.  That is, it can invoke the same delegate that the lambda is being assigned to.  As with any recursive method, you need to make sure that there is termination logic to prevent the recursion from continuing indefinitely.

Below is an example of a simple recursive lambda expression.

            // Must assign delegate so that we can
            // reference it in lambda
            Action<int> countdown = null;
                
            countdown = (i) =>
            {
                if (i > 0)
                { 
                    Console.WriteLine(i);
                    countdown(i-1);
                }
            };

            countdown(5);

1190-001