about
Bits: Generic C#
07/13/2024
0
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 andIEnumerable<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
1.0 Enumeration
{
IEnumerator<T> GetEnumerator();
}
{
T Current { get; }
bool MoveNext();
void Reset();
}
1.1 Related Interfaces
{
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();
}
{
Object this [int index] { get; set; }
int IndexOf(T item);
void Insert(int index, T item);
void RemoveAt(int index);
}
1.2 Lambda Expressions
1.3 Delegates
1.3.1 Standard Delegates
Proxy for methods that take no parameters and return nothing. | |
Represents methods that take a single argument and return nothing. | |
Proxy for methods that accept an argument of type |
2.0 Source Code
2.1 Standard Generic Library Types
/*-----------------------------------------------------
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
2.2.1 BasicPoint<T> and Point<T> Definitions
/*-------------------------------------------------------------------
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 ontheir coordinate members. ⇐ Import Analysis and Display classes. ⇐
Compilation will fail if the class does
not implement all of the methods declared
with these interfaces.
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:
implicitly uses
to
implicitly uses
to
⇐
and implements
With these additional interfaces,
instances will be accepted by generic functions
that expect standard collections.
2.2.2 BasicPoint<T> and Point<T> Demonstration
/*-----------------------------------------------------
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
2.3.1 Generic Indexer
/*-----------------------------------------------------
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
/*-----------------------------------------------------
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
/*-----------------------------------------------------
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
/*-----------------------------------------------------
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
Analysis and Display Functions
/*-------------------------------------------------------------------
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:
methods are defined in a class.
same role as free functions in other languages.
types that improves developer productivity,
code readability and maintainability.
2.4.1 Complex Generic Methods
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());
}
that will be useful in other programs, but are
not used in this demonstration.
niques or other designs that are interesting.
2.5 Program Structure
/*-------------------------------------------------------------------
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
ments all of the demonstrations in this
Generic C# Bit.
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
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 |