Define and test a C++ style transform extension method for IList<> and IDictionary<>

Radical Edward 0 Tallied Votes 146 Views Share

One problem with C# is that if you want to modify a collection, you have to manually iterate over it. The generic List class supports ForEach, but there's no method that allows global list changes. C# is kind of restrictive in what changes you can make while iterating, so Edward wrote a couple of simple extension methods to avoid repeating the same pattern. These methods allow transformations on all values in an IList or IDictionary based on a delegate.

Also included is a RandomSet class for testing with random samples.

In case it's not obvious, this code only compiles as C# 3. ;)

using System;
using System.Collections;
using System.Collections.Generic;

namespace EdRules {
  static class Program {
    /// <summary>
    /// Test driver for new extension methods
    /// </summary>
    static void Main() {
      Test("Testing IList extension methods...",
        TestListTransform);
      Test("Testing IDictionary extension methods...",
        TestDictionaryTransform);
    }

    /// <summary>
    /// Simple test scaffolding for methods
    /// </summary>
    /// <param name="message">
    /// Custom message when starting the test
    /// </param>
    /// <param name="testHandler">The method to test</param>
    static void Test(string message, Func<bool> testHandler) {
      Console.WriteLine(message);

      string result = testHandler() ? "Successful test!" : "Failed test.";

      Console.WriteLine(result);
      Console.WriteLine("================= Done =================");
    }

    /// <summary>
    /// Test the generic Transform extension for IList
    /// </summary>
    /// <returns>True on success, false on failure</returns>
    private static bool TestListTransform() {
      var values = new List<double>();

      values.AddRange(new RandomSet(5));

      Console.WriteLine("Before transform:");
      values.ForEach(item => Console.WriteLine("\t{0}", item));

      // Transformation: Shift to an integer range of [0,1000)
      values.Transform(item => item * 1000);

      Console.WriteLine("After transform:");
      values.ForEach(item => Console.WriteLine("\t{0}", (int)item));

      // Successful return
      return true;
    }

    /// <summary>
    /// Test the generic Transform extension for IDictionary
    /// </summary>
    /// <returns>True on success, false on failure</returns>
    private static bool TestDictionaryTransform() {
      var values = new Dictionary<string, int>();

      values.Add("one", 1);
      values.Add("two", 2);
      values.Add("three", 3);
      values.Add("four", 4);

      Console.WriteLine("Before transform:");

      foreach (string key in values.Keys)
        Console.WriteLine("\t{0}:\t{1}", key, values[key]);

      // Transformation: Square the values
      values.Transform(item => item * item);

      Console.WriteLine("After transform:");

      foreach (string key in values.Keys)
        Console.WriteLine("\t{0}:\t{1}", key, values[key]);

      // Successful return
      return true;
    }
  }

  /// <summary>
  /// A set of randomly generated double precision
  /// values in the range of [0,1) for random samples
  /// </summary>
  public class RandomSet: IEnumerable<double> {
    /// <summary>
    /// The current set of random values
    /// </summary>
    public List<double> Values { get; set; }

    /// <summary>
    /// Create a new random set of the specified size
    /// </summary>
    /// <param name="size">The number of values to generate</param>
    public RandomSet(int size) {
      Random rand = new Random();

      this.Values = new List<double>(size);

      // Use doubles because they can easily be 
      // transformed into other useful forms
      for (int i = 0; i < size; ++i)
        this.Values.Add(rand.NextDouble());
    }

    #region Enumeration Interface
    /// <summary>
    /// Returns a generic enumerator that
    /// enumerates through the random set
    /// </summary>
    /// <returns></returns>
    public IEnumerator<double> GetEnumerator() {
      foreach (double value in this.Values)
        yield return value;
    }

    /// <summary>
    /// Returns an enumerator that enumerates
    /// through the random set
    /// </summary>
    /// <returns></returns>
    IEnumerator IEnumerable.GetEnumerator() {
      return GetEnumerator();
    }
    #endregion
  }

  /// <summary>
  /// Localized implementation for better organization
  /// of .NET framework extension methods
  /// </summary>
  public static partial class FrameworkExtensionMethods {
    /// <summary>
    /// Interface for transforming a single generic item
    /// </summary>
    /// <typeparam name="T">The type of the item to transform</typeparam>
    /// <param name="value">The original item before transformation</param>
    /// <returns>The transformed item (may be a copy)</returns>
    public delegate T TransformAction<T>(T value);

    /// <summary>
    /// Transform all items in a generic 
    /// list with a user specified action
    /// </summary>
    /// <typeparam name="ValueType">
    /// The type of items contained in the list
    /// </typeparam>
    /// <param name="list">
    /// This method is an extension to the generic list
    /// </param>
    /// <param name="action">
    /// The transformation action to perform on each item
    /// </param>
    public static void Transform<ValueType>(
      this IList<ValueType> list, TransformAction<ValueType> action) {

      // Replace each item with the transformation
      for (int i = 0; i < list.Count; ++i)
        list[i] = action(list[i]);
    }

    /// <summary>
    /// Transform all values in a generic dictionary 
    /// with a user specified action
    /// </summary>
    /// <typeparam name="KeyType">
    /// The type of keys contained in the dictionary
    /// </typeparam>
    /// <typeparam name="ValueType">
    /// The type of values contained in the dictionary
    /// </typeparam>
    /// <param name="dict">
    /// This method is an extension to the generic dictionary
    /// </param>
    /// <param name="action">
    /// The transformation action to perform on each value
    /// </param>
    public static void Transform<KeyType, ValueType>(
      this IDictionary<KeyType, ValueType> dict,
      TransformAction<ValueType> action) {

      // Enumerating over keys while making changes is 
      // tricky. Make a copy of keys to make things easier
      KeyType[] keys = new KeyType[dict.Keys.Count];

      dict.Keys.CopyTo(keys, 0);

      // Replace each value with the transformation
      foreach (KeyType key in keys)
        dict[key] = action(dict[key]);
    }
  }
}
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.