Sunday, June 3, 2012

type-changing across .Select

Chapter 8 of C# 4.0 in a Nutshell is on LINQ Queries and dives into Lambdas too. Here is some copy straight from page 317:

The type arguments in Func appear in the same order they do in lambda expressions. Hence, Func<TSource, bool> matches a TSource=>bool lambda expression: one that accepts a TSource argument and returns a bool value. Similarly, Func<TSource,TResult> matches a TSource=>TResult lambda expression.

 
 

An example of shape changing with .Select is given on the next page. I warp it slightly here:

using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
namespace Whatever.Tests
{
   [TestClass]
   public class SelectTest
   {
      [TestMethod]
      public void Test()
      {
         string[] names = {"Tom", "Dick", "Harry", "Mary", "Jay"};
         IEnumerable<int> query = names.Select(n => n.Length);
         Assert.AreEqual(query.Count(), 5);
         Assert.AreEqual(query.ElementAt(0), 3);
         Assert.AreEqual(query.ElementAt(1), 4);
         Assert.AreEqual(query.ElementAt(2), 5);
         Assert.AreEqual(query.ElementAt(3), 4);
         Assert.AreEqual(query.ElementAt(4), 3);
      }
   }
}

 
 

Now here is some of my own experimentation with type-changing across .Select:

using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
using Whatever.Models;
namespace Whatever.Tests
{
    [TestClass]
    public class SelectTest
    {
      private Order[] GetOrders()
      {
         Order[] orders = new Order[]
            {
               new Order()
                  {
                     Total = 17.33m,
                     PurchaserAddress = new StreetAddress()
                        {
                           StreetNumber = 500,
                           StreetName = "East Stassney",
                           SuiteNumber = 1212,
                           City = "Austin",
                           Region = "Texas",
                           ZipCode = 78745
                        },
                     BillingAddress = new StreetAddress()
                        {
                           StreetNumber = 1,
                           StreetName = "South Broad",
                           SuiteNumber = null,
                           City = "Globe",
                           Region = "Arizona",
                           ZipCode = 85501
                        },
                     ShipToAddress = new StreetAddress()
                        {
                           StreetNumber = 5055,
                           StreetName = "Club",
                           SuiteNumber = null,
                           City = "West Palm Beach",
                           Region = "Florida",
                           ZipCode = 33415
                        }
                  },
               new Order()
                  {
                     Total = 88.39m,
                     PurchaserAddress = new StreetAddress()
                        {
                           StreetNumber = 1515,
                           StreetName = "Ocean",
                           SuiteNumber = null,
                           City = "Santa Cruz",
                           Region = "California",
                           ZipCode = 95060
                        },
                     BillingAddress = new StreetAddress()
                        {
                           StreetNumber = 7434,
                           StreetName = "North Lannon",
                           SuiteNumber = 1033,
                           City = "Lannon",
                           Region = "Wisconsin",
                           ZipCode = 53046
                        },
                     ShipToAddress = new StreetAddress()
                        {
                           StreetNumber = 1515,
                           StreetName = "Ocean",
                           SuiteNumber = null,
                           City = "Santa Cruz",
                           Region = "California",
                           ZipCode = 95060
                        }
                  },
               new Order()
                  {
                     Total = 5.47m,
                     PurchaserAddress = new StreetAddress()
                        {
                           StreetNumber = 503,
                           StreetName = "West Third",
                           SuiteNumber = null,
                           City = "Forest",
                           Region = "Mississippi",
                           ZipCode = 39074
                        },
                     BillingAddress = new StreetAddress()
                        {
                           StreetNumber = 503,
                           StreetName = "West Third",
                           SuiteNumber = null,
                           City = "Forest",
                           Region = "Mississippi",
                           ZipCode = 39074
                        },
                     ShipToAddress = new StreetAddress()
                        {
                           StreetNumber = 503,
                           StreetName = "West Third",
                           SuiteNumber = null,
                           City = "Forest",
                           Region = "Mississippi",
                           ZipCode = 39074
                        }
                  }
            };
         return orders;
      }
      
      [TestMethod]
      public void Test()
      {
         Order[] orders = GetOrders();
         IEnumerable<StreetAddress> query = orders.Select(n => n.BillingAddress);
         Assert.AreEqual(query.Count(), 3);
         Assert.AreEqual(query.ElementAt(0).City, "Globe");
         Assert.AreEqual(query.ElementAt(1).City, "Lannon");
         Assert.AreEqual(query.ElementAt(2).City, "Forest");
      }
   }
}

 
 

In this example Order looks like so:

namespace Whatever.Models
{
   public class Order
   {
      public decimal Total { get; set; }
      public StreetAddress PurchaserAddress { get; set; }
      public StreetAddress BillingAddress { get; set; }
      public StreetAddress ShipToAddress { get; set; }
   }
}

 
 

In this example StreetAddress looks like so:

namespace Whatever.Models
{
   public class StreetAddress
   {
      public int StreetNumber { get; set; }
      public string StreetName { get; set; }
      public int? SuiteNumber { get; set; }
      public string City { get; set; }
      public string Region { get; set; }
      public int ZipCode { get; set; }
   }
}

 
 

I further spruced up what I wrote like so...

[TestMethod]
public void Test()
{
   Order[] orders = GetOrders();
   IEnumerable<List<StreetAddress>> query = orders.Select(n => n.Addresses());
   IEnumerable <StreetAddress> addresses = query.Flatten();
   Assert.AreEqual(addresses.Count(), 6);
   Assert.AreEqual(addresses.ElementAt(0).City, "Austin");
   Assert.AreEqual(addresses.ElementAt(1).City, "Globe");
   Assert.AreEqual(addresses.ElementAt(2).City, "West Palm Beach");
   Assert.AreEqual(addresses.ElementAt(3).City, "Santa Cruz");
   Assert.AreEqual(addresses.ElementAt(4).City, "Lannon");
   Assert.AreEqual(addresses.ElementAt(5).City, "Forest");
}

 
 

This required writing an extension method for Order.

using System.Collections.Generic;
using Whatever.Models;
public static class OrderExtensions
{
   public static List<StreetAddress> Addresses(this Order Order)
   {
      List<StreetAddress> addresses = new List<StreetAddress>();
      addresses.Add(Order.PurchaserAddress);
      if (Order.PurchaserAddress.Equals(Order.BillingAddress))
      {
         if (!Order.PurchaserAddress.Equals(Order.ShipToAddress))
         {
            addresses.Add(Order.ShipToAddress);
         }
      } else {
         addresses.Add(Order.BillingAddress);
         if (!Order.PurchaserAddress.Equals(Order.ShipToAddress))
         {
            if (!Order.BillingAddress.Equals(Order.ShipToAddress))
            {
               addresses.Add(Order.ShipToAddress);
            }
         }
      }
      return addresses;
   }
}

 
 

...and another extension method for Flatten.

using System.Collections.Generic;
using System.Linq;
using Whatever.Models;
public static class StreetAddressExtensions
{
   public static IEnumerable<StreetAddress> Flatten(this
         IEnumerable<List<StreetAddress>> layeredCollection)
   {
      List<StreetAddress> addresses = new List<StreetAddress>();
      if (layeredCollection.Count() > 0)
      {
         addresses.AddRange(layeredCollection.ElementAt(0));
         if (layeredCollection.Count() > 1)
         {
            foreach(List<StreetAddress> list in layeredCollection)
            {
               foreach(StreetAddress address in list)
               {
                  bool alreadyInCollection = false;
                  foreach(StreetAddress datum in addresses)
                  {
                     if (datum.Equals(address))
                     {
                        alreadyInCollection = true;
                     }
                  }
                  if (!alreadyInCollection) addresses.Add(address);
               }
            }
         }
      }
      return addresses;
   }
}

 
 

I had to do override the Equals method on StreetAddress by adding this method to the class. I believe that when one does not use the override keyword, as is the case, here, that this is technically method hiding and not method overriding.

public bool Equals(StreetAddress alt)
{
   bool equality = true;
   if (StreetNumber != alt.StreetNumber) equality = false;
   if (StreetName != alt.StreetName) equality = false;
   if (SuiteNumber != alt.SuiteNumber) equality = false;
   if (City != alt.City) equality = false;
   if (Region != alt.Region) equality = false;
   if (ZipCode != alt.ZipCode) equality = false;
   return equality;
}

No comments:

Post a Comment