about
Bits_Generic C#
05/30/2023
Bits Repo Code Bits Repo Docs

Bits_Generic C#

code, output, and build for C# on Windows, macOS, and Linux

This page is a prototype used to decide which language features to emphasize. It will be replaced with a final version soon.

Synopsis:

This page demonstrates uses of C# generic functions and types.
  • C# generics support definition of function and class patterns, which can be instantiated for a variety of different concrete types.
  • Each instantiation must use types that provide all the facilities used in the pattern.
  • The pattern can provide constraints on its type parameters that guarantee methods and properties required by the pattern.
Demo Notes  
All of the languages covered in this demonstration support generics with the exception of JavaScript. Three of the languages: C++, Rust, and C# provide generics. Python provides a typing module that provides type hints, checked with a static analysis tool. Each generic function or class is a pattern for defining functions and classes of a specific type. Thus a generic is a pattern for making patterns. Python and JavaScript, are dynamically typed and already support defining functions and classes for multiple types, e.g., no need for generics. This demonstration illustrates use of generic functions and classes, which for C++, Rust, and C#, are processed by the compiler.
The examples below show how to use library and user defined types with emphasis on illustrating syntax and basic operations.

1.0 CodeSnaps

Source Code - Program.cs

/*-------------------------------------------------------------------
  Cs_Generic::Program.cs
  - Demonstrates creation and assignment of objects from classes with
    generic type parameter(s).
*/
using System;
using System.Collections;
using System.Collections.Generic;   // IEnumerable<T>, List<T>
using System.Linq;                  // IEnumerable<T>.ToArray
using System.Text;                  // StringBuilder
using Points;                       // defined in Points.cs
using Analysis;                     // defined in Analysis.cs

/*
  Static Data Types:
    value types => assignment copies value
    --------------------------------------
      sbyte, byte, short, int, long, ushort, uint, ulong
      float, double
      decimal, char, bool
      struct
    Reference types => assignment copies reference
    ----------------------------------------------
      object, string, array, class
    ----------------------------------------------
      strings are immutable, so any change requires copy on write.
      That simulates value behavior.
*/
namespace CSharpGenerics
{
  /*-- Generic demonstration begins here --*/

  class Program
  {
    const string nl = "\n";
    static void Main(string[] args)
    {
      Display.ShowLabel(" Demonstrate C# generics");

      Display.ShowNote(
        "Examples of creation and display of Library Types\n" +
        "- size is the size of reference, not instance\n" +
        "- String is not generic, but ShowTypeScalar(...),\n" +
        "  used below to display String, is generic\n" +
        "- Generic function ShowTypeEnum(...) is used, below,\n" +
        "  for both List<T> and Dictionary<K,V>", nl
      );
      /*-- ShowTypeScalar is a generic function --*/
      string aString = "a string";  // hides construction
      Display.ShowTypeScalar(aString, "aString", nl);

      string another = new string("another string");
      Display.ShowTypeScalar(another, "another", nl);

      List<double> aList =
        new List<double>{ 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0 };

      Display.ShowTypeEnum(aList, "aList", 5, nl);

      var d1 = new Dictionary<int, string>
      {
        { 0, "zero"}, {1, "one"}, {2, "two"}
      };
      Display.ShowTypeEnum(d1, "d1", 5, nl);

      Display.ShowNote(
        "Example of user-defined type:\n" +
        "- PointN<T>, a point with N generic coordinates"
      );

      PointN<double> p1 = new PointN<double>(7);
      p1.coor = new List<double> {
        1.5, 2.0, 3.5, 4.0, 5.5, 6.0, 7.5
      };
      p1.Show("p1");

      Display.ShowNote(
        "PointN<T> is both Enumerable and Showable. That is,\n" +
        "it implements both IEnumerable<T> and IShow.\n" +
        "- so both ShowTypeEnum and ShowTypeShowable work"
      );
      Display.ShowOp("ShowTypeShowable(p1, \"p1\")");
      p1.Width = 6;
      Display.ShowTypeShowable(p1, "p1", nl);

      Display.ShowOp("ShowTypeEnum(p1, \"p1\")");
      Display.ShowTypeEnum<double>(p1, "p1", p1.Width, nl);

      Display.ShowOp("set left(2) and width(7)");
      PointN<int> p2 = new PointN<int>(7);
      p2.coor = new List<int>{1, 2, 3, 4, 5, 6, 7};
      Display.ShowOp("p2.Show(\"p2\")");
      p2.left(2).width(7);
      p2.Show("p2");
      Console.WriteLine();

      Display.ShowTypeShowable(p2, "p2", nl);

      Display.ShowNote(
        "Test formatting for Enumerable types", nl
      );

      int[] testarr = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
      Display.ShowTypeEnum(testarr, "testarr", 5, nl);

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

          

Source Code - PointsGeneric.cs

/*-------------------------------------------------------------------
  PointsGeneric.cs
  - provides definitions for user-defined class PointN<T>
*/

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

namespace Points {
  /*----------------------------------------------------------------------
    PointN<T> is a generalization of Point4D
     - holds any finite number of generic coordinates
     - coordinates are held in a List<T>
     - implements IEnumerable<T> so it can be indexed and iterated
     - it is a reference type because it is implemented with a class.
       its List<T> is also a reference type
  */
  public class PointN<T> : IEnumerable<T> , Analysis.IShow {
    /*--------------------------------------------------------------------
      Constructs a point with N coordinates each with default value
    */
    public PointN(int N) {
      coor = new List<T>();
      for(int i = 0; i<N; ++i) {
        T? test = default(T);
        if(test != null) {
          coor.Add(test);
        }
      }
    }
    /* translates IShow::show() for needs of Point2 class */
    public void Show(string name) {
      PrintSelf(name);
    }
    /*
      Displays structure and state of N-dimensional point.
      - 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{0}", Display.Indent(Left));
      Console.WriteLine("}");
    }
    /* The three functions below support indexing and iterating */
    public T this[int index] {
      get { return coor[index]; }
      set { coor.Insert(index, value); }
    }
    public IEnumerator<T> GetEnumerator() {
      return coor.GetEnumerator();
    }
    IEnumerator IEnumerable.GetEnumerator() {
      return this.GetEnumerator();
    }
    public List<T> coor { get; set; }
    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 PointN<T> width(int w) {
      this.Width = w;
      return this;
    }
    public PointN<T> left(int l) {
      this.Left = l;
      return this;
    }
    public PointN<T> indent(int i) {
      this.Indent = i;
      return this;
    }
  }
}
          

Source Code - AnalysisGeneric.cs

/*-------------------------------------------------------------------
  AnalysisGeneric.cs
  - provides several type analysis and display methods in the
    class Display
*/

using System.Reflection;
using System.Reflection.Emit;
using System.Collections;
using System.Collections.Generic;   // IEnumerable<T>, List<T>
using System.Linq;                  // IEnumerable<T>.ToArray
using System.Text;

namespace Analysis {
  /*----------------------------------------------------------------------
      IShow 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);   // 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
  }

  public class Display {
    /*-------------------------------------------------
      Show static type with some formatting adjustments
      and name at callsite.
    */
    public static String Spaces(int i) {
      return new String(' ', i);
    }
    public void Println(String s) {
      Console.WriteLine(s);
    }
    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
    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 {
      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());
    }
    /*-------------------------------------------------
    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 = ""
    )
    {
      ShowType(t, nm);
      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);
    }
    /*-------------------------------------------------
    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
    */
    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);
    }
    /*-------------------------------------------------
    Emphasize text with borders
    */
    public static void ShowNote(string s, string suffix = "") {
      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();
    }
    /*
      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 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;
    }
    /*----------------------------------------------------------------
      Utils 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());
    }
  }
}

          

Output

C:\github\JimFawcett\Bits\CSharp\Cs_Generic
> dotnet run

--------------------------------------------------
 Demonstrate C# generics
--------------------------------------------------

--------------------------------------------------
Examples of creation and display of Library Types
- size is the size of reference, not instance
- String is not generic, but ShowTypeScalar(...),
  used below to display String, is generic
- Generic function ShowTypeEnum(...) is used, below,
  for both List<T> and Dictionary<K,V>
--------------------------------------------------
aString: String, size: 8, reference type
value: "a string"

another: String, size: 8, reference type
value: "another string"

aList: List<T>, size: 8, reference type
value:
  aList {
    1, 1.5, 2, 2.5, 3,
    3.5, 4
  }

d1: Dictionary<T>, size: 8, reference type
value:
  d1 {
    [0, zero], [1, one], [2, two]
  }

--------------------------------------------------
Example of user-defined type:
- PointN<T>, a point with N generic coordinates
--------------------------------------------------
  p1 {
    1.5, 2, 3.5, 4, 5.5,
    6, 7.5
  }
--------------------------------------------------
PointN<T> is both Enumerable and Showable. That is,
it implements both IEnumerable<T> and IShow.
- so both ShowTypeEnum and ShowTypeShowable work
--------------------------------------------------
--- ShowTypeShowable(p1, "p1") ---
p1: PointN<T>, size: 8, reference type
value:
  p1 {
    1.5, 2, 3.5, 4, 5.5, 6,
    7.5
  }

--- ShowTypeEnum(p1, "p1") ---
p1: PointN<T>, size: 8, reference type
value:
  p1 {
    1.5, 2, 3.5, 4, 5.5, 6,
    7.5
  }

--- set left(2) and width(7) ---
--- p2.Show("p2") ---
  p2 {
    1, 2, 3, 4, 5, 6, 7
  }

p2: PointN<T>, size: 8, reference type
value:
  p2 {
    1, 2, 3, 4, 5, 6, 7
  }

--------------------------------------------------
Test formatting for Enumerable types
--------------------------------------------------

testarr: Int32[], size: 8, reference type
value:
  testarr {
    0, 1, 2, 3, 4,
    5, 6, 7, 8, 9
  }


That's all Folks!

C:\github\JimFawcett\Bits\CSharp\Cs_Generic
>
          

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
          

2.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

3.0 References

Reference Description
C# Generics - tutorialspoint Basic syntax with examples
C# Tutorial - w3schools Interactive examples
C# Reference - Microsoft Relatively complete informal language reference
  bottom refs VS Code bld out anal pts src codeSnaps top
  Next Prev Pages Sections About Keys