about
Bits: Generic C#
07/13/2024
0
Bits Repo Code Bits Repo Docs

Bits: Iter C#

Iterate through library and user-defined types

Synopsis:

This page demonstrates uses of C# iteration.
  • C# iteration acts on types that implement the IEnumerable and IEnumerable<T> interfaces. These include:
    List<T>, Dictionary<K, V>, string, and user-defined types that implement them.
  • C# arrays do not explicitly implement the IEnumerable<T> interface, but the CLR code generation process implicitly creates methods needed for enumeration.
  • The C# foreach loop implicitly uses methods of the enumeration interfaces to simplify iteration through collections.
Demo Notes  
All of the languages covered in this demonstrtion support iteration using iterators. C# iterators are provided by enumerable containers and implement a public interface with the method bool MoveNext(); and property T Current { get; }. This demonstration illustrates use of iterators with a series of functions that provide code, in several forms, for iterating through containers. It includes demos for library types and for a user-defined point class.

1.0 Enumeration


C# provides a rich set of enumeration facilities for standard and user-defined collection types. The four interfaces presented below show how to step through collections and build functions that accept collections for modification and display. Enumeration over C# collections is based on two interfaces. The first provides access to an enumeration object:
public interface IEnumerable<T> : IEnumerable, IDisposable
{
    IEnumerator<T> GetEnumerator();
}
The base IEnumerable interface declares the same GetEnumerator() method, but that returns a non-generic enumerator. Deriving from this base supports access to enumerators for either generic or non-generic collections. The IDisposable interface declares a Dispose() method for preemtively enqueuing an instance for the next resource collection.
The Second interface provides methods to step through, and return values from, enumerable collections using the enumerator returned from GetEnumerator().
public interface IEnumerator<T> : IEnumerator, IDisposable
{
   T Current { get; }
   bool MoveNext();
   void Reset();
}
Deriving from the non-generic base interface, IEnumerator, supports indexing both generic and non-generic collections.

1.1 Related Interfaces

There are two other interfaces frequently associated with enumeration: ICollection<T> which provides operations that focus on the collection as a whole, and IList<T> which provides methods for managing individual elements.
public interface ICollection<T> : IEnumerable<T>, IEnumerable
{
   int Count { get; }
   bool Contains(T item);
   void CopyTo(T[] array, int index);
   bool IsReadOnly { get; }
   void Add(T item);
   bool Remove(T item);
   void Clear();
}
Deriving from the non-generic base interface, IEnumerable, supports working with both generic and non-generic collections. The IList<T> interface supports indexing operations on the collecton. This doesn't have to be just lists. It is often implemented by user-defined types, and the native C# array is provided with these methods by the CLR during translation.
public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
{
   Object this [int index] { get; set; }
   int IndexOf(T item);
   void Insert(int index, T item);
   void RemoveAt(int index);
}
Deriving from the non-generic base interface, IEnumerable, supports indexing both generic and non-generic collections.

1.2 Lambda Expressions

Lambdas are used in several of the demonstrations below. Here's what you need to know about them for this topic: A lambda expression defines an anonymous function of the form:
(a1, a2, ...) => { e1; e2; ... }
where a1, a1, ... are arguments and e1, e2, ... are expressions, each of which is terminated with a semicolon, making them statements. The return type of a lambda expression is inferred.
If there is only a single input argument the left-hand parentheses may be omitted. If there is only a single statement, the right-hand braces may be omitted. An example is:
x => x * x;
During compilation, lamda expressions are converted to private methods

1.3 Delegates

A delegate type defines a proxy for some lambda or method:
delegate R DelegateName(T t, U u, ...);
defines a proxy for any method that accepts arguments of type T, U, ... and returns a value of type R. It is instantiated by assigning to it the name of some function or lambda with the same signature:
int Square(int x) => x * x;
delegate int DoOp(int x);
DoOp = Square
Sometime later:
var sqrd = DoOp(5);
Delegates are often used for event handling or plug-ins for processing that is published as a utility, supporting user defined operations. The utility defines and exposes a delegate as part of its public interface. Then users supply a specific operation invoked by the delegate.

1.3.1 Standard Delegates

The .Net std library defines many delegates. Here we list a few of the most common:
Action Proxy for methods that take no parameters and return nothing.
Action<T> Represents methods that take a single argument and return nothing.
Func<T, R> Proxy for methods that accept an argument of type T and return a value of type R.

2.0 Source Code

Examples below show how to use library and user defined types with emphasis on illustrating syntax and basic operations. These demonstrations of iteration in C# code are partitioned into modules Program.cs, PointsIter.cs, and AnalysisIter.cs. Discussion of each of these will be presented in separate sections of the page, below, accessed from links in the left panel.

2.1 Standard Generic Library Types

Standard IEnumerable<T> types are defined in the .Net Standard Library and include many more than shown here. We have selected three frequently used types for demonstration: arrays, lists, and dictionaries. You can find details of the others from the references. The examples below show how to use library and user defined types with emphasis on illustrating syntax and basic operations. Alter splitter-bar panel widths by dragging splitter bar or clicking in either panel to expand that panel's width. Default widths are set by setting width style on left panel.
/*-----------------------------------------------------
  Enumerate standard library types
*/
static void EnumStdTypes() {

  Display.ShowNote("  Enumerate standard types", "\n");

  Display.ShowOp("array - basic enumeration");
  int[] arri = { 0, 1, 2, 1, 0 };
  var enm = arri.GetEnumerator();
  enm.MoveNext();
  Console.Write("  {0}", enm.Current);
  while (enm.MoveNext()) {
    Console.Write(", {0}", enm.Current);
  }
  Console.WriteLine("\n");

  Display.ShowOp("enumerate list with foreach");
  List<double> ld = new List<double>{1.0, 1.5, 2.0, 2.5, 3.0 };
  bool first = true;
  foreach (var item in ld) {
    if (first) {
      Console.Write("  {0:0.0}", item);
      first = false;
    }
    else {
      Console.Write(", {0:0.0}", item);
    }
  }
  Console.WriteLine("\n");

  Display.ShowOp("enumerate dictionary with foreach");
  Dictionary<string, int> dsi =
    new Dictionary<string, int>{
      {"zero", 0}, {"one", 1}, {"two", 2}, {"three", 3}, {"four", 4},
    };
  first = true;
  foreach (var item in dsi) {
    if (first) {
      Console.Write("  {0}", item);
      first = false;
    }
    else {
      Console.Write(", {0}", item);
    }
  }
  Console.WriteLine("\n");
}
  
--------------------------------------------------
  Enumerate standard types
--------------------------------------------------




--- array - basic enumeration ---
  0, 1, 2, 1, 0








--- enumerate list with foreach ---
  1.0, 1.5, 2.0, 2.5, 3.0












--- enumerate dictionary with foreach ---
  [zero, 0], [one, 1], [two, 2], [three, 3], [four, 4]

2.2 User-defined Custom Types

C# defines custom types with the "class" keyword. Classes are patterns for laying out instances in memory and define operations for the instance data. Generic classes use one or more generic parameters as placeholders for specific defined types. That supports providing a single design that works for multiple types, improving code abstraction and developer productivity.

2.2.1 BasicPoint<T> and Point<T> Definitions

Point<T> is similar to the generic types Point<T, N> defined for C++ and Rust. There is one fundamental difference; the C++ template and Rust generic point types each take an integer parameter N. The C# generic point class does not. C# does not support adding integer types to a generic parameter list, e.g., <T, N> is not allowed. The BasicPoint<T> type fully implements enumeration and indexing capability. However, to be accepted by functions that expect standard collections, we need to implement ICollection<T> and IList<T> interfaces. For that reason, we also define the derived Point<T> type.
/*-------------------------------------------------------------------
  Points.cs
  - provides definitions for user-defined class BasicPoint<T>
*/

using System.Collections;
using System.Collections.Generic;   // IEnumerable<T>, List<T>
using Analysis;

namespace Points {

  /*----------------------------------------------------------------------
    BasicPoint<T>
     - holds any finite number of generic coordinates
     - coordinates are held in a List<T>
     - implements IEnumerable<T> so it can be indexed and iterated
     - does not implement ICollection<T> or IList<T> to avoid making
       demonstration too complicated for learning example
     - it is a reference type because it is implemented with a class.
       its List<T> is also a reference type
  */
  public class BasicPoint<T> : IEnumerable<T>, Analysis.IShow {
    /*--------------------------------------------------------------------
      Constructs a BasicPoint with N coordinates each with default value
    */
    public BasicPoint(int N) {
      coor = new List<T>();
      for(int i = 0; i<N; ++i) {
        T? test = default(T);
        if(test != null) {
          coor.Add(test);
        }
      }
    }
    /*--------------------------------------------------------------------
      Supports building BasicPoint by Adding elements after construction
    */
    public BasicPoint() {
      coor = new List<T>();
    }
    /*-----------------------------------------------------
      Add element to back of coordinate list
      - supports using list initializer, e.g.
        var p = new BasicPoint<int> { 1, 2. 3 }
    */
    public void Add(T t) {
      coor.Add(t);
    }
    /* translates IShow::show() for needs of BasicPoint class */
    public void Show(string name) {
      PrintSelf(name);
    }
    /*
      Displays structure and state of N-dimensional BasicPoint.
      - state is a set of rows of coordinate data
      - property Width specifies number of elements in each row
      - property Left specifies offset of row from terminal Left edge
    */
    public void PrintSelf(string name) {
      Console.Write(Display.Indent(Left));
      Console.Write("{0} {{\n{1}", name, Display.Indent(Left + 2));
      for(int i=0; i<coor.Count; ++i) {
        Console.Write("{0}", coor[i]);
        if(i < coor.Count - 1) {
          Display.print(", ");
        }
        if((i+1) % Width == 0 && i != coor.Count - 1) {
          Console.Write("\n");
          Console.Write(Display.Indent(Left + Indent));
        }
      }
      Console.Write("\n" + Display.Indent(Left + Indent) + "{0}", dt);
      Console.Write("\n{0}", Display.Indent(Left));
      Console.WriteLine("}");
    }
    /* The functions below support indexing and iterating */
    public T this[int index] {
      get { return coor[index]; }
      set { coor.Insert(index, value); }
    }
    /*-- returns coor enumerator --*/
    public IEnumerator<T> GetEnumerator() {
      return coor.GetEnumerator();
    }
    /*-- returns BasicPoint<T> enumerator --*/
    IEnumerator IEnumerable.GetEnumerator() {
      return this.GetEnumerator();
    }
    /*-- returns BasicPoint<T> iterator --*/
    public IEnumerable<T> iter() {
      foreach (var item in coor) {
        yield return item;
      }
    }
    public List<T> coor { get; set; }
    public DateTime dt { get; set; } = DateTime.Now;
    public int Length { get { return coor.Count; } }
    public int Width { get; set; } = 5;   // default row size
    public int Left { get; set; } = 2;    // default offset
    public int Indent { get; set; } = 2;
    /* initializers */
    public BasicPoint<T> width(int w) {
      this.Width = w;
      return this;
    }
    public BasicPoint<T> left(int l) {
      this.Left = l;
      return this;
    }
    public BasicPoint<T> indent(int i) {
      this.Indent = i;
      return this;
    }
  }
  /*-------------------------------------------------------
    Point<T>
    - same as BasicPoint<T> with additions:
      - implements ICollection<T> and IList<T> interfaces
      - single inheritance of BasicPoint<T>'s implementation
      - Deriving from ICollecton<T> and IList<T> supports using
        generic functions for analysis and display.
  */
  public class Point<T> :
    BasicPoint<T>, ICollection<T>, IList<T>
  {
    public Point(int N) : base(N) { }
    public Point() : base() {}
    public void Clear() {
      base.coor.Clear();
    }
    public bool Contains(T item) {
      return base.coor.Contains(item);
    }
    public void CopyTo(T[] array, int i) {
      base.coor.CopyTo(array, i);
    }
    public bool Remove(T item) {
      return base.coor.Remove(item);
    }
    public int Count {
      get { return base.coor.Count; }
    }
    public bool IsReadOnly {
      get { return false; }
    }
    public int IndexOf(T item) {
      return base.coor.IndexOf(item);
    }
    public void Insert(int i, T item) {
      base.coor.Insert(i, item);
    }
    public void RemoveAt(int index) {
      base.coor.RemoveAt(index);
    }
  }
}

BasicPoint<T> and Point<T>

Reference types parameterized on T, the type of
their coordinate members.
Import Analysis and Display classes.
BasicPoint<T> implements
IEnumerable<T> and IShow interfaces.
Compilation will fail if the class does
not implement all of the methods declared
with these interfaces.
Constructor: BasicPoint(int n)
Unlike C++ and Rust, C# cannot use integral
types as generic parameters, so the number of
coordinates is passed as a constructor argument.
Initialized Constructor:
BasicPoint<int> { 1, 2, 3, 4, 5 }
implicitly uses Add(T t) to add elements
to coor.
Indexer:
BasicPoint<int> { 1, 2, 3, 4, 5 }
implicitly uses Add(T t) to add elements
to coor.
Display format:
Width sets number of elements per row
Left sets left margin
Indent sets offset of data within delimiters
Point<T> inherits from BasicPoint<T>
and implements ICollection<T> and
IList<T> interfaces.

With these additional interfaces, Point<T>
instances will be accepted by generic functions
that expect standard collections.

2.2.2 BasicPoint<T> and Point<T> Demonstration

For this demonstration either BasicPoint<T> or Point<T> can be used interchangeably; all we need is to satisfy the IEnumeration<T> interface.
/*-----------------------------------------------------
  Enumerate Point<T>
  - all of the examples here work with either
    BasicPoint<T> or Point<T>
*/
static void EnumPoint() {

  Display.ShowNote("  Enumerate Point types", "\n");

  Display.ShowOp("BasicPoint - indexing");
  BasicPoint<int> bpi = new BasicPoint<int>() { 0, 1, 2, 3, 4 };
  Console.Write("  {0}", bpi[0]);
  for (int i = 1; i < bpi.Length; i++) {
    Console.Write(", {0}", bpi[i]);
  };
  Console.WriteLine("\n");

  Display.ShowOp("BasicPoint - basic enumeration");
  // BasicPoint<int> bpi = new BasicPoint<int>() { 0, 1, 2, 3, 4 };
  var enm = bpi.GetEnumerator();
  enm.MoveNext();
  Console.Write("  {0}", enm.Current);
  while (enm.MoveNext()) {
    Console.Write(", {0}", enm.Current);
  }
  Console.WriteLine("\n");

  Display.ShowOp("Point enumeration with foreach");
  Point<double> pd = new Point<double>() { 0.0, -1.5, 3.0, -4.5, 6.0 };
  bool first = true;
  foreach (var item in pd) {
    if (first) {
      Console.Write("  {0:0.0}", item);
      first = false;
    }
    else {
      Console.Write(", {0:0.0}", item);
    }
  }
  Console.WriteLine("\n");
}






--------------------------------------------------
  Enumerate Point types
--------------------------------------------------

--- BasicPoint - indexing ---
  0, 1, 2, 3, 4





--- BasicPoint - basic enumeration ---
  0, 1, 2, 3, 4








--- Point enumeration with foreach ---
  0.0, -1.5, 3.0, -4.5, 6.0

2.3 Generic Functions Definition

Generic functions need to bound their generic parameters with constraints ensuring that all methods called on any of them are actually implemented by the argument's types. If that isn't done, compilation will fail. The demonstrations below show how that is done for a variety of useful cases. Each example starts with the definition of a generic function that does some form of enumeration, followed by an executor function that shows how the demonstration function can be used.

2.3.1 Generic Indexer

This first demonstration displays elements of a collection using indexing. That requires only the ICollection<T> and IList<T> interfaces.
/*-----------------------------------------------------
  void GenericIndexer<T>(List<T>)
  - Display list items with indexing
  - works for all sequential indexable containers
*/
static void GenericIndexer<C, T>(C coll)
    where C:ICollection<T>, IList<T>
{
  Console.Write("  {0}", coll[0]);
  for(int i=1; i < coll.Count(); ++i) {
    Console.Write(", {0}", coll[i]);
  }
  Display.Println("");
}
static void executeGenericIndexer() {
  Display.ShowNote("  GenericIndexer<C, T>(C coll)", nl);

  Display.ShowOp("index over int[]");
  int[] ai = new int[] {1, 2, 3, 4, 5};
  GenericIndexer<int[], int>(ai);
  Console.WriteLine();

  Display.ShowOp("index over List<int>");
  List<int> li = new List<int>{1, 2, 3, 2, 1};
  GenericIndexer<List<int>, int>(li);
  Console.WriteLine();
  /*
    This demo cannot use BasicPoint<T> because it does not
    implement ICollection<T> and IList<T>.

  */
  Display.ShowOp("index over Point<int>");
  Point<int> pi = new Point<int>{1, 2, 3, 2, 1};
  GenericIndexer<Point<int>, int>(pi);
  Console.WriteLine();
}













--------------------------------------------------
  GenericIndexer<C, T>(C coll)
--------------------------------------------------

 --- index over int[] ---
  1, 2, 3, 4, 5



 --- index over List ---
  1, 2, 3, 2, 1







 --- index over Point ---
  1, 2, 3, 2, 1

2.3.2 Generic Enumeration

The function GenericEnumerator<T>(IEnumerable<T> enm) accepts only enumerables and so needs no additional constraints.
/*-----------------------------------------------------
  void GenericEnumerator<T>(IEnumerable<T> enm)
  - Display list items with enumerator
*/
static void GenericEnumerator<T>(IEnumerable<T> enm) {
  var lenum = enm.GetEnumerator();
  lenum.MoveNext();
  Console.Write("  {0}", lenum.Current);
  while(lenum.MoveNext()) {
    var item = lenum.Current;
    Console.Write(", {0}", item);
  }
  Display.Println("");
}
static void executeGenericEnumerator() {
  Display.ShowNote(
    "  GenericEnumerator<T>(IEnumerable<T> enm)"
  );
  Console.WriteLine();

  Display.ShowOp("enumerate over int[]");
  int[] ai = new int[] {1, 2, 3, 4, 5};
  GenericEnumerator<int>(ai);
  Console.WriteLine();

  Display.ShowOp("enumerate over String");
  String s = "a string";
  GenericEnumerator<char>(s);
  Console.WriteLine();

  Display.ShowOp("enumerate over List<double>");
  List<double> ld = new List<double>{1.0, 2.25, 3.5, 2.75, 1.0};
  GenericEnumerator<double>(ld);
  Console.WriteLine();

  Display.ShowOp("enumerate over Dictionary<String, int>");
  Dictionary<String, int> d = new Dictionary<String, int> {
    {"zero", 0}, {"one", 1}, {"two", 2}
  };
  GenericEnumerator<KeyValuePair<String,int>>(d);
  Console.WriteLine();

  Display.ShowOp("enumerate over BasicPoint<int>");
  BasicPoint<int> pb = new BasicPoint<int>{ 1, 2, 3, 2, 1 };
  GenericEnumerator<int>(pb);
  Console.WriteLine();

  Display.ShowOp("enumerate over Point<int>");
  Point<int> p = new Point<int>{ 1, 2, 3, 2, 1 };
  GenericEnumerator<int>(p);
  Console.WriteLine();
}















--------------------------------------------------
  GenericEnumerator(IEnumerable enm)
--------------------------------------------------


--- enumerate over int[] ---
  1, 2, 3, 4, 5



--- enumerate over String ---
  a,  , s, t, r, i, n, g



--- enumerate over List<double> ---
  1, 2.25, 3.5, 2.75, 1



--- enumerate over Dictionary<String, int> ---
  [zero, 0], [one, 1], [two, 2]





--- enumerate over BasicPoint<int> ---
  1, 2, 3, 2, 1



--- enumerate over Point<int> ---
  1, 2, 3, 2, 1

2.3.3 Generic ForEach

The example below uses a foreach loop that applies a lambda to each element of its input enumerable. The lambda has the type Action<T> which is a standard delegate taking a single argument and has no return value.
/*-----------------------------------------------------
  void GenericForEach<T>(IEnumerable<T> enm)
  - Display list items with foreach
*/
static void GenericForEach<T>(
  IEnumerable<T> enm, Action<T> lambda
) {
  foreach (T item in enm) {
    lambda(item);
  }
}
static void ExecuteGenericForEach() {

  Display.ShowNote(
    "  GenericForEach<T>(
    IEnumerable<T> enm, Action<T> lambda )" ); Console.WriteLine(); Display.ShowOp("Add 1.0 to items of List<double>"); List<double> ld =     new List<double>{1.0, 2.25, 3.5, 2.25, 1.0}; Console.Write(" "); /* delegates pass their arguments by value, e.g., make a copy, so plus_one will not modify the original enumerable element. The next demo shows how to do that. */ Action<double> plus_one = x => Console.Write("{0} ",x += 1.0); /* Action<T> is delegate with no return and input T */ GenericForEach<double>(ld, plus_one); Console.WriteLine("\n"); /* verify that the list was not modified by uncommenting the lines below */ // GenericEnumerator<double>(ld); // Console.WriteLine("\n"); /* Lambdas can capture local variables, and those can be modified, as shown below. */ List<double> modLd = new List<double>(); /* using captured variable modLd */ Action<double> add_one = item => { modLd.Add(item + 1.0); }; /* using ld defined earlier, add_one adds ld item + 1 to modLd with function call below */ GenericForEach<double>(ld, add_one); /* modLd now modified */ Display.ShowOp("original list"); GenericEnumerator<double>(ld); Console.WriteLine(); Display.ShowOp("modified list using lambda capture"); GenericEnumerator<double>(modLd); Console.WriteLine(); /* repeat same demonstration with BasicPoint */ Display.ShowOp("original BasicPoint"); var pb = new BasicPoint<double>{ 1.0, 2.5, 4.0 }; GenericEnumerator<double>(pb); Console.WriteLine(); /* we need a new lambda because we are capturing a BasicPoint, not a List */ var mpb = new BasicPoint<double>(); Action<double> add_one_point = item => { mpb.Add(item + 1.0); }; GenericForEach<double>(pb, add_one_point); Display.ShowOp("modified BasicPoint using lambda capture"); GenericEnumerator<double>(mpb); Console.WriteLine(); Display.ShowOp("modified BasicPoint Show(name)"); mpb.Show("mpb"); }













--------------------------------------------------
  GenericForEach(
    IEnumerable enm, Action lambda
  )
--------------------------------------------------


--- Add 1.0 to items of List ---
  2 3.25 4.5 3.25 2






























--- original list ---
  1, 2.25, 3.5, 2.25, 1

--- modified list using lambda capture ---
  2, 3.25, 4.5, 3.25, 2



--- original BasicPoint ---
  1, 2.5, 4









--- modified BasicPoint using lambda capture ---
  2, 3.5, 5

--- modified BasicPoint Show(name) ---
  mpb {
    2, 3.5, 5
    7/16/2024 3:21:16 PM
  }

2.3.4 Generic Modifier

The right panel is also pre styling with terminal colors, intended for output display.
/*-----------------------------------------------------
  IEnumerable<T> 
  GenericModifier<T>(IEnumerable<T> enm, Func<T,T> lambda)
  - return modified IEnumerable<T> transformed by 
    lambda Func<T,T>
  - uses Linq Select
*/
static IEnumerable<T> GenericModifier<T>(
    IEnumerable<T> enm, Func<T,T> lambda) 
{
  /*
    lambda makes copies of input enumerable's elements,
    so lambda's modifications don't affect input.
    Instead we make new modified collection using
    Linq Select method.
  */
  return enm.Select(lambda);
}
static void ExecuteGenericModifier() {

  Display.ShowNote(
    "  IEnumerable<T> 
       GenericModifier<T>(\n    Ienumerable<T> enm, Func<T,T> lambda\n  )"
  );
  Console.WriteLine();

  Display.ShowOp("GenericModifier for List<int> with plus_one");
  /*-- original --*/
  List<int> li = new List<int>{1, 2, 3, 4, 3, 2};
  Console.WriteLine("Original:");
  GenericEnumerator<int>(li);
  /*-- modified --*/
  Func<int, int> plus_one = x => x+1;
  /* Func<U,V> is a delegate with input U and output V */
  var coll = GenericModifier<int>(li, plus_one);  // modify
  Console.WriteLine("Modified:");
  GenericEnumerator<int>(coll);                   // display
  Console.WriteLine();

  Display.ShowOp("GenericModifier for array<double> with square");
  /*-- original --*/
  double[] da = new double[]{ 1.0, 2.5, 3.0, 4.5 };
  Console.WriteLine("Original:");
  GenericEnumerator<double>(da);
  /*-- modified --*/
  Func<double,double> square = x => x*x;
  var coll2 = GenericModifier<double>(da, square);  // modify
  Console.WriteLine("Modified:");
  GenericEnumerator<double>(coll2);                 // display
  Console.WriteLine();

  Display.ShowOp("GenericModifier for BasicPoint<double> with square");
  /*-- original --*/
  var pm = new BasicPoint<double> { 1.0, 2.5, -5.0, 7.5 };
  Console.WriteLine("Original:");
  GenericEnumerator<double>(pm);
  /*-- modified --*/
  Console.WriteLine("Modified:");
  var pm2 = GenericModifier<double>(pm, square);  // modify
  GenericEnumerator<double>(pm2);                 // display
  Console.WriteLine();
}




















--------------------------------------------------
  IEnumerable GenericModifier(
    Ienumerable enm, Func lambda
  )
--------------------------------------------------

--- GenericModifier for List<int> with plus_one ---
Original:
  1, 2, 3, 4, 3, 2
Modified:
  2, 3, 4, 5, 4, 3








--- GenericModifier for array<double> with square ---
Original:
  1, 2.5, 3, 4.5
Modified:
  1, 6.25, 9, 20.25







--- GenericModifier for BasicPoint<double> with square ---
Original:
  1, 2.5, -5, 7.5
Modified:
  1, 6.25, 25, 56.25

2.4 Analysis Library

A number of analysis and display methods have been used throughout the examples above. All of them have been discussed in earlier Bits in this sequence, so we do not show them by default. They are here so that you can easily find their definitions if you need that information to understand example code.
Analysis and Display Functions The Analysis library provides 14 methods, of which 9 are generic. They illustrate the power of generics to provide reusable, configurable code.
/*-------------------------------------------------------------------
  Analysis.cs
  - provides several type analysis and display methods in the
    class Display
*/
using System;
using System.Collections;
using System.Collections.Generic;     // IEnumerable<T>, List<T>
using System.Linq;                    // IEnumerable<T>.ToArray
using System.Text;                    // StringBuilder
using System.Reflection;
using System.Reflection.Emit;         // managed size
using System.Runtime.InteropServices; // GCHandle

namespace Analysis {
  /*----------------------------------------------------------
    IShow interface allows analysis functions to operate on
    instances of any class that implements it.
    - See ShowTypeShowable<T>(T t, ...), below.
  */
  public interface IShow {
    void Show(string name, int Width = 5, int Left = 2);   // show instance state as rows of elements
    int Length { get; }       // total number of elements
    int Width { get; set; }   // number of elements per row
    int Left { get; set; }    // offset from terminal Left
    int Indent {get; set; }   // element indent from braces
  }
  /*-----------------------------------------------------------------------
    Collection of static functions that present information about types
    and data values on the terminal line.
  */
  public class Display {
    public static String Spaces(int i) {
      return new String(' ', i);
    }
    public static bool IsAssociativeColl(IEnumerable coll) {
      return coll is IDictionary;
    }
    /*-------------------------------------------------
      Show static type with some formatting adjustments
      and name at callsite.
    */
    public static void ShowType<T>(T t, string nm) {
      Type tt = t!.GetType();
      string tnm = tt.Name;
      if(tt.IsGenericType) {
        if(tnm.Length > 1) {
          tnm = tnm.Remove(tnm.Length - 2) + "<T>";
        }
      }
      Console.Write("{0}: {1}, ", nm, tnm);
      int size = GetManagedSize(tt);
      Console.Write("size: {0}, ", size);
      if (tt.IsValueType) {
        Console.WriteLine("value type");
      }
      else {
        Console.WriteLine("reference type");
      }
    }
    /*-------------------------------------------------
      Display information about the type of any scalar type.
      - scalar types are those with a single value like:
          int, double, string, ...
      - This function directly uses only simple reflection
    */
    public static void ShowTypeScalar<T>(
      T t, string nm, string suffix = ""
    )
    {
      ShowType(t, nm);
      Console.WriteLine("value: \"{0}\"{1}", t, suffix);
    }
    /*-------------------------------------------------
      Show type information for any type that implements
      IEnumerable<T> interface.
    */
    public static void ShowTypeEnum<T> (
    IEnumerable<T> t, string nm, int w = 5, string suffix = ""
    )
    {
      Type tt = t!.GetType();
      string tnm = tt.Name;
      if(tt.IsGenericType) {
        if(tnm.Length > 1) {
          tnm = tnm.Remove(tnm.Length - 2);
          if(IsAssociativeColl(t)) {
            tnm += "<K,V>";
          }
          else {
            tnm += "<T>";
          }
        }
      }
      Console.Write("{0}: {1}, ", nm, tnm);
      int size = GetManagedSize(tt);
      Console.Write("size: {0}, ", size);
      if (tt.IsValueType) {
        Console.WriteLine("value type");
      }
      else {
        Console.WriteLine("reference type");
      }
      Console.WriteLine("value:\n{0}{1} {{", "  ", nm);
      /*
        beautify value list into rows of w elements
        indented by 4 spaces
      */
      string tmp = FoldArray(t.ToArray(), w, 4);
      Console.Write(tmp);
      Console.WriteLine("\n  }");
      Console.Write(suffix);
    }
    /*-------------------------------------------------
      Show type information for any type that implements
      the IShow interface
    */
    public static void ShowTypeShowable<T>(
      T t, string nm, string suffix=""
    )
    where T:IShow
    {
      ShowType(t, nm);
      Console.WriteLine("value:");
      t.Show(nm);     // guaranteed by IShow implementation
      Console.Write(suffix);
    }
    /*-------------------------------------------------
      Provides name of caller, nm, as label for IShow() information.
      - works for all Showable instances
    */
    public static void ShowLabeledObject<T>(
      T t, string nm
    ) where T:IShow {
      // Console.Write(nm);
      t.Show(nm);
    }
    /*-------------------------------------------------
      Provides name of caller, nm, as label for value.
      - works for all T with ToString.
    */
    public static void DisplayLabeledObject<T>(T t, string nm) {
      Console.WriteLine("{0}: {1}", nm, t!.ToString());
    }
    /*-------------------------------------------------
      create string of count spaces, used to offset output
    */
    public static string Indent(int count) {
      StringBuilder sb = new StringBuilder();
      sb.Append(' ', count);
      return sb.ToString();
    }
    /*-------------------------------------------------
      Truncate string to length of N, but only if
      its length is greater than N
    */
    public static string truncate(int N, string bigStr) {
      if(bigStr.Length <= N) {
        return bigStr;
      }
      StringBuilder sb =  new StringBuilder();
      sb.Append(bigStr);
      sb.Length = N;  // move back pointer to desired length
      return sb.ToString();
    }
    /*-------------------------------------------------
      fold array elements into rows of w elements
    */
    public static string FoldArray<T>(T[] arr, int w, int Left) {
      StringBuilder tmp = new StringBuilder();
      tmp.Append(Indent(Left));
      for(int i=0; i< arr.Length; ++i) {
        tmp.Append(arr[i]!.ToString());
        tmp.Append(", ");
        if((i+1) % w == 0 && i != arr.Length - 1) {
          tmp.Append("\n");
          tmp.Append(Indent(Left));
        }
      }
      if(tmp.Length > 1) {
        tmp.Length -= 2;  // don't return last comma and space
      }
      return tmp.ToString();
    }
    /*-------------------------------------------------
      do t1 and t2 share the same address?
    */
    public static void IsSameObj<T>(
      T t1, String n1, T t2, String n2, string suffix = ""
    ) {
      if(ReferenceEquals(t1, t2)) {
        Console.WriteLine(
          "{0} is same object as {1}{2}", n1, n2, suffix
        );
    }
      else {
        Console.WriteLine(
        "{0} is not same object as {1}{2}", n1, n2, suffix);
      }
    }
    /*-------------------------------------------------
      Beware, two distinct objects may have same hashcode.
      - Not used in this demo for that reason.
    */
    public static void showIdent<T>(
      T t, String n, string suffix = ""
    ) {
      int id = t!.GetHashCode();
      Console.WriteLine("{0}, {1}{2}", n, id, suffix);
    }
    /*-------------------------------------------------
      Display function call or operation to help turn
      output data into information
    */
    public static void ShowOp(string op, string suffix = "") {
      Console.WriteLine("--- {0} ---{1}", op, suffix);
    }
    public static void print(String s = "") {
      Console.Write(s);
    }
    public static void println(String s = "") {
      Console.WriteLine(s);
    }
    public static void Println(String s) {
      Console.WriteLine(s);
    }
    /*-------------------------------------------------
      Emphasize text with borders
    */
    public static void ShowNote(string s, int n=60, string suffix = "") {
      string line = new string('-', n);
      Console.WriteLine(
        "--------------------------------------------------"
      );
      Console.WriteLine("{0}", s);
      Console.WriteLine(
        "--------------------------------------------------{0}",
        suffix
      );
    }
    /*-------------------------------------------------
      Surround note with empty lines
    */
    public static void ShowLabel(string s) {
      Console.WriteLine();
      ShowNote(s);
      Console.WriteLine();
    }
  }
}

Generic Methods:

C# does not support free functions. Instead,
methods are defined in a class.
However, static methods of classes play the
same role as free functions in other languages.
interface IShow:
Allows function:
ShowTypeShowable<T>(T t, ...)
to accept any type T that implements IShow.
Generic functions provide abstraction over
types that improves developer productivity,
code readability and maintainability.
ShowTypeEnum<T>(IEnumerable<T> t, ...)
Can display any IEnumerable<T> type, e.g.
Array<T>, List<T>, Point<T>.
ShowTypeShowable(T t, ...)

2.4.1 Complex Generic Methods

The dropdown below provides code for interesting generic methods, some of which use advanced techniques. They are useful know, but none are used in this demonstration.
Complex Methods
/*-----------------------------------------------------------
  All methods below this are not part of this presentation
  - may use advanced code techniques
  - may be experimental
  - may simply be alternate designs that could be
    useful in other applications
-----------------------------------------------------------*/
/*
  Show fields and methods for either reference or value types
  using reflection
*/
// https://stackoverflow.com/questions/7613782/iterating-through-struct-members
public static void iterate<T>(T t) /*where T:new()*/ {
  Console.WriteLine("fields:");
  foreach(
    var field in typeof(T).GetFields(
      BindingFlags.Instance |
      BindingFlags.NonPublic |
      BindingFlags.Public
    )
  ) {
    Console.WriteLine(
      "  {0} = {1}", field.Name, field.GetValue(t)
    );
  }
  Console.WriteLine("methods:");
  foreach(
    var method in typeof(T).GetMethods(
      BindingFlags.Instance | BindingFlags.Public
    )
  ) {
    Console.WriteLine(
      "  {0}", method.Name
    );
  }
}
/*-----------------------------------------------------
Build string representation of array of type T
-----------------------------------------------------*/
public static string ToStringRepArray<T>(T[] arr) {
  StringBuilder sb = new StringBuilder();
  sb.Append("{ ");
  bool first = true;
  foreach(T item in arr) {
    if(item == null) {
      break;
    }
    if(first) {
      sb.Append(item.ToString());
      first = false;
    }
    else {
      sb.AppendFormat(", {0}", item);
    }
  }
  sb.Append(" }\n");
  return sb.ToString();
}
/*-----------------------------------------------------
Build string representation of IEnumerable
collection T<U>. Works for array too.
-----------------------------------------------------*/
public static string ToStringRepIEnumerable<T,U>(T enu)
  where T:IEnumerable<U>
{
  StringBuilder sb = new StringBuilder();
  sb.Append("[ ");
  bool first = true;
  foreach(U item in enu) {
    if(item == null) {
      break;
    }
    if(first) {
      sb.Append(item.ToString());
      first = false;
    }
    else {
      sb.AppendFormat(", {0}", item);
    }
  }
  sb.Append(" ]\n");
  return sb.ToString();
}
/*-----------------------------------------------------
Direct implementation of enumerating associative
collection.  This can also be done with
ToStringRepIEnumerable<Dict,KVPair>(dict).
-----------------------------------------------------*/
public static string ToStringRepAssocCont<Dict,Key,Value>(Dict assoc)
  where Dict:IDictionary<Key,Value>
{
  StringBuilder sb = new StringBuilder();
  sb.Append("{ ");
  bool first = true;
  foreach(var item in assoc) {
    if(first) {
      var sf = String.Format("{{{0}, {1}}}", item.Key, item.Value);
      sb.Append(sf);
      first = false;
    }
    else {
      sb.AppendFormat(", {{{0}, {1}}}", item.Key, item.Value);
    }
  }
  sb.Append(" }\n");
  return sb.ToString();
}
/*-- move this to functions --*/
static void DemoPassValAndRef() {
  Display.ShowNote("Pass by value", 60, "\n");
  double d = 3.1415927;
  Pass_by_value<double>(d, "d");
  TestForNullValue(d, "d");

  List<int> li = new List<int>{ 1, 2, 3, 2, 1 };
  Pass_by_value<List<int>>(li, "li");
  TestForNullValue(li, "li");
}
public static string GetTypeString<T>(T t, String nm, String suffix = "")
{
  /*-- t! asserts that t is not null --*/
  Type tt = t!.GetType();
  string typeInfo = String.Format("{0}: Type: {1}\n", nm, tt.Name);
  int size = GetManagedSize(tt);
  string instanceInfo = String.Format("value: {0}\nsize: {1}{2}", t, size, suffix);
  return typeInfo + instanceInfo;
}
static unsafe void Pass_by_value<T>(T? t, string nm) {
  string ts = GetTypeString(t, nm);
  Console.WriteLine(ts);
  /*
    Suppresses warning about taking address of managed type.
    The pointer is used only to show the address of ptr
    as part of analysis of copy operations.
  */
  #pragma warning disable 8500
  string addrd = ToStringAddress<T>(&t);
  #pragma warning restore 8500
  Console.WriteLine("{0}: {1}", nm, addrd);
  t = default(T);
  /*
    caller sees this change if and only if T is a reference type
    in which case t is null.
  */
}
#pragma warning disable 8500
public static unsafe string ToStringAddress<T>(T* ptr) {
  if(ptr == null) {
    return "";
  }
  IntPtr addr = (IntPtr)ptr;
  string addrstr = string.Format("address: 0x" + addr.ToString("x"));
  return addrstr;
}
#pragma warning restore 8500
/*-- extract address of reference instance in managed heap --*/
public static string ToStringAddressFromHandle<T>(T t) {
  string addrstr = "for handle\n";
  try {
    GCHandle handle = GCHandle.Alloc(t, GCHandleType.Pinned);
    IntPtr address = handle.AddrOfPinnedObject();
    addrstr = "address: " + String.Format("0x" + address.ToString("x"));
    handle.Free();
    return addrstr + "\n";
  }
  catch {
    Console.WriteLine("GCHandle exception thrown");
  }
  return addrstr;
}
static void TestForNullValue<T>(T? t, string nm) {
  if(t == null) {
    Console.WriteLine(nm + " is null");
  }
  else {
    Console.WriteLine(nm + " is {0}", t);
  }
}
static void DemoPrimitives() {
  Display.ShowNote(
    "Examples of creation and display of Primitive Types",
    60
  );
  short s = 123;
  Display.ShowTypeScalar(s, "s", "\n");
  long l = 12345;
  Display.ShowTypeScalar(l, "l", "\n");
  float f = 3.1415927f;
  Display.ShowTypeScalar(f, "f", "\n");
  double d = 3.1415927;
  Display.ShowTypeScalar(d, "d", "\n");
  int[] arr = new int[]{ 4, 3, 2, 1, 0, -1};
  Display.ShowTypeScalar(arr, "arr");
}
/*-------------------------------------------------
  Build string of comma separated values from
  Enumerable collection
  - no longer used here, but will be useful so kept
*/
// https://stackoverflow.com/questions/330493/join-collection-of-objects-into-comma-separated-string
public static string ToCSV<T>(IEnumerable<T> coll) {
  StringBuilder sb = new StringBuilder();
  foreach(T elem in coll) {
    sb.Append(elem!.ToString()).Append(", ");
  }
  return sb.ToString(0, sb.Length - 2);
}
/*-------------------------------------------------
  Returns value of T for IEnumerable<T> at runtime.
  Needed for some functions that operate on generic
  collections.
  - at this time, not used in this demo
*/
// https://www.codeproject.com/Tips/5267157/How-To-Get-A-Collection-Element-Type-Using-Reflect
public static Type? GetTypeOfCollection(Object coll) {
  Type type = (coll).GetType();
  var etype = typeof(IEnumerable<>);
  foreach (var bt in type.GetInterfaces()) {
    if (bt.IsGenericType && bt.GetGenericTypeDefinition() == etype) {
      return (bt.GetGenericArguments()[0]);
    }
  }
  return null;
}
/*----------------------------------------------------------------
  This method uses advanced relection

  - GetMangedSize(Type type) is function that returns the size of
  value types and handles, used to help discover how things work.
  It is placed here because it uses advanced techniques that
  will eventually be covered elsewhere in this site.  Knowing
  how it works is not essential for the things we are examining
  in this demo.
*/
// https://stackoverflow.com/questions/8173239/c-getting-size-of-a-value-type-variable-at-runtime/8173293#8173293
public static int GetManagedSize(Type type)
{
  // all this just to invoke one opcode with no arguments!
  var method = new DynamicMethod(
    "GetManagedSizeImpl", typeof(uint), new Type[0],
    typeof(TypeExtensions), false
  );

  ILGenerator gen = method.GetILGenerator();
  gen.Emit(OpCodes.Sizeof, type);
  gen.Emit(OpCodes.Ret);

  var func = (Func<uint>)method.CreateDelegate(typeof(Func<uint>));
  return checked((int)func());
}
These functions provide additional functionality
that will be useful in other programs, but are
not used in this demonstration.
Some of them use advanced reflection tech-
niques or other designs that are interesting.

2.5 Program Structure

The code below, Program.cs, imports libraries: Points and Analysis. Using those facilities it orchestrates all the demonstrations shown here by calling in Main methods developed above Main. Details of those methods have been elided to show the structure of this program. All of the elided code has already been shown in the code blocks above.
/*-------------------------------------------------------------------
  Cs_Iter::Program.cs
  - Demonstrates IEnumerable interface and iteration
*/
using System;
using System.Collections;
using System.Collections.Generic;   // IEnumerable<T>, List<T>
using System.Linq;                  // IEnumerable<T>.ToArray, select
using System.Text;                  // StringBuilder
using Points;                       // defined in Points.cs
using Analysis;                     // defined in Analysis.cs

/*-----------------------------------------------
  Note:
  Find all Bits code, including this in
  https://github.com/JimFawcett/Bits
  You can clone the repo from this link.
-----------------------------------------------*/
/*
  This demo uses Indexing and Enumerator to iterate
  through std collections:
  - String, List<T>, Dictionary<K,V>
  and through user-defined type:
  - BasicPoint<T>
*/
namespace CSharpIter
{
  using Analysis;

  class Program
  {
    /*-----------------------------------------------------
      Enumerate standard library types
    */
    static void EnumStdTypes() {
      /* code elided */
    }
    /*-----------------------------------------------------
      Enumerate Point<T>
      - all of the examples here work with either
        BasicPoint<T> or Point<T>
    */
    static void EnumPoint() {
      /* code elided */
    }
    /*-----------------------------------------------------
      void GenericIndexer<T>(List<T>)
      - Display list items with indexing
      - works for all sequential indexable containers
    */
    static void GenericIndexer<C, T>(C coll)
       where C:IEnumerable, ICollection<T>, IList<T>
    {
      /* code elided */
    }
    static void executeGenericIndexer() {
      /* code elided */
    }
    /*-----------------------------------------------------
      void GenericEnumerator<T>(IEnumerable<T> enm)
      - Display list items with enumerator
    */
    static void GenericEnumerator<T>(IEnumerable<T> enm) {
      /* code elided */
    }
    static void executeGenericEnumerator() {
      /* code elided */
    }
    /*-----------------------------------------------------
      void GenericForEach<T>(IEnumerable<T> enm)
      - Display list items with foreach
    */
    static void GenericForEach<T>(IEnumerable<T> enm, Action<T> lambda) {
      /* code elided */
    }
    static void ExecuteGenericForEach() {
      /* code elided */
    }
    /*-----------------------------------------------------
      IEnumerable<T> GenericModifier<T>(IEnumerable<T> enm, Func<T,T> lambda)
      - return modified IEnumerable<T> transformed by lambda Func<T,T>
      - uses Linq Select
    */
    static IEnumerable<T> GenericModifier<T>(IEnumerable<T> enm, Func<T,T> lambda) {
      /* code elided */
    }
    static void ExecuteGenericModifier() {
      /* code elided */
    }
    /*-----------------------------------------------------
      Begin demonstration
    */
    static void Main(string[] args)
    {
      Display.ShowLabel(" Demonstrate C# iteration");

      EnumStdTypes();
      EnumPoint();
      executeListIndexer();
      executeGenericIndexer();
      executeGenericEnumerator();
      ExecuteGenericForEach();
      ExecuteGenericModifier();

      Console.WriteLine("\nThat's all Folks!\n");
    }
  }
}

Program

This code block shows how Program.cs imple-
ments all of the demonstrations in this
Generic C# Bit.
Code details, shown elsewhere, have been
elided to show clearly the structure of
this demonstration.

3.0 Build


C:\github\JimFawcett\Bits\CSharp\Cs_Generic
> dotnet build
MSBuild version 17.5.1+f6fdcf537 for .NET
  Determining projects to restore...
  All projects are up-to-date for restore.
  Cs_Objects -> C:\github\JimFawcett\Bits\CSharp\Cs_Generic\bin\Debug\net7.0\Cs_Ob  
  jects.dll

Build succeeded.
    0 Warning(s)
    0 Error(s)

Time Elapsed 00:00:01.79
C:\github\JimFawcett\Bits\CSharp\Cs_Generic
          

4.0 VS Code View

The code for this demo is available in github.com/JimFawcett/Bits. If you click on the Code dropdown you can clone the repository of all code for these demos to your local drive. Then, it is easy to bring up any example, in any of the languages, in VS Code. Here, we do that for CSharp\Cs_Generic. Figure 1. VS Code IDE - C# Generics

4.0 References

Reference Description
C# Generics - tutorialspoint Basic syntax with examples
C# Tutorial - w3schools Interactive examples
C# Reference - Microsoft Relatively complete informal language reference