about
Bits_Generic C#
05/30/2023
Bits_Generic C#
code, output, and build for C# on Windows, macOS, and Linux
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
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
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 |