about
Bits: Generic C#
07/11/2024
0
Bits: Generic C#
generic library and user-defined types
Synopsis:
This page demonstrates uses of C# generic functions and types.- C# generics support definition of class and method 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 Generics and Constraints
Examples in Section 2.6.1
/* implementing code using T */
}
where T: BC | // derive from or equal to class BC |
where T: IF | // implement interface IF |
where T: class | // must be a reference type |
where T: struct | // must be value type |
where T: new() | // can be created with new() |
... |
where T: C1, C2, ...
{
SomeType(T arg1, ...) {
/* code to initialize instance */
}
/* code declaring additional SomeType methods and data */
}
2.0 Source Code
2.1 Standard Generic Library Types
static void Demonstrate_Std_Library_Types() {
Display.ShowNote("Demonstrate std library generic types", nl);
Display.ShowOp("String[] array processed by generic function");
String[] array = {"one", "two", "three"};
Display.ShowTypeEnum(array, "array", 5, nl);
Display.ShowOp("List<double>");
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);
Display.ShowOp("Dictionary<int, String>");
var d1 = new Dictionary<int, String>
{
{ 0, "zero"}, {1, "one"}, {2, "two"}
};
Display.ShowTypeEnum(d1, "d1", 5, nl);
}
--------------------------------------------------
Demonstrate std library generic types
--------------------------------------------------
--- String[] array processed by generic function ---
array: String[], size: 8, reference type
value:
array {
one, two, three
}
--- List ---
aList: List, size: 8, reference type
value:
aList {
1, 1.5, 2, 2.5, 3,
3.5, 4
}
--- Dictionary ---
d1: Dictionary, size: 8, reference type
value:
d1 {
[0, zero], [1, one], [2, two]
}
1.2 User-defined Custom Types
1.2.1 Hello<T> Definition
public class Hello<T> where T:new() {
public Hello() {
datum = new T();
}
public Hello(T t) {
datum = t;
}
public void show(String nm) {
Console.Write(" {0}: {{ {1} }}\n", nm, datum);
}
public T datum { get; set; } // property
}
Hello<T>:
is a placeholder of specific concrete types, e.g.,
of concrete classes, one for each concrete type declared in
program code. The generic parameter is required to provide a constructor
with the constraint
with get and set operations and two overloaded constructors,
one that initializes the property value to a default value
and one that initializes the property to a specified value. It also defines a function
a representation of the instance on the terminal. This is a simple example designed to illustrate syntax for
C# classes. More details follow in following code blocks.
1.2.2 Hello<T> Demonstration
Display.ShowOp("Hello<int>");
Hello<int> hi = new Hello<int>(42);
Display.ShowType(hi, "hi");
Console.WriteLine("value:");
hi.show("h");
Display.ShowOp("Hello<double>");
Hello<double> hd = new Hello<double>(3.1415927);
Console.WriteLine("access hd.datum: {0}", hd.datum);
hd.show("hd");
Console.WriteLine();
--- Hello<int> ---
hi: Hello<T>, size: 8, reference type
value:
h: { 42 }
--- Hello<double> ---
access hd.datum: 3.1415927
hd: { 3.1415927 }
1.2.3 Stats<T> Definition
/*-------------------------------------------------------------------
Stats.cs
- provides definitions for user-defined class Stats<T>
*/
using System.Collections;
using System.Collections.Generic; // IEnumerable<T>, List<T>, ...
using System.Numerics; // INumber<T>
using Analysis;
namespace Stats {
/*----------------------------------------------------------------------
Stats<T> is a generic demo type
- holds any finite number of generic values
- computes several statistics on its values
*/
public class Stats<T>
where T: INumber<T>
{
/*--------------------------------------------------------------------
Constructs a point with N coordinates each with default value
*/
public List<T> items { get; set; }
public Stats(List<T> l) {
items = l;
}
public void check() {
if (items == null) {
throw new SystemException("stats is null");
}
else if (items.Count < 1)
throw new SystemException("stats is empty");
}
public int size() {
check();
return items.Count;
}
public T max() {
check();
T max = items[0];
foreach(T item in items) {
if(max.CompareTo(item) < 0)
max = item;
}
return max;
}
public T min() {
check();
T min = items[0];
foreach(T item in items) {
if(min.CompareTo(item) > 0)
min = item;
}
return min;
}
public T? sum() {
check();
dynamic? sum = default(T);
if(sum != null) {
foreach(T item in items) {
sum += item;
}
return sum;
}
return default(T);
}
public double avg() {
check();
dynamic? sum = this.sum();
return Convert.ToDouble(sum)/Convert.ToDouble(items.Count);
}
}
}
Stats<T>:
This demonstration shows how a user-definedclass can implement arithmetic operations on
its data members. In order for that to work the generic parameter
arithmetic operations. That's achieved with the
bound
holding a
inside getter and setter methods that can, if
needed, control access to and modifications of
the data. How to do that is shown here. ⇐
To make the property immutable, simply remove
1.2.4 Stats<T> Demonstration
Display.ShowOp("Stats<int>");
List<int> tmp1 = new List<int> { 1, 2, 3, 2, 1 };
Stats<int> si = new Stats<int>(tmp1);
ShowList(tmp1);
Console.WriteLine(" max: {0}", si.max());
Console.WriteLine(" min: {0}", si.min());
Console.WriteLine(" sum: {0}", si.sum());
Console.WriteLine(" avg: {0:0.00}", si.avg());
Console.WriteLine();
Display.ShowOp("Stats<double>");
List<double> tmp2 = new List<double> { 1.0, 1.5, 2.0, 1.5, 1.0 };
Stats<double> sd = new Stats<double>(tmp2);
ShowList(tmp2);
Console.WriteLine(" max: {0:0.0}", sd.max());
Console.WriteLine(" min: {0:0.0}", sd.min());
Console.WriteLine(" sum: {0:0.0}", sd.sum());
Console.WriteLine(" avg: {0:0.00}", sd.avg());
Console.WriteLine();
--- Stats<int> ---
[ 1, 2, 3, 2, 1 ]
max: 3
min: 1
sum: 9
avg: 1.80
--- Stats<double> ---
[ 1, 1.5, 2, 1.5, 1 ]
max: 2.0
min: 1.0
sum: 7.0
avg: 1.40
1.2.5 Point<T> Definition
/*-------------------------------------------------------------------
Points.cs
- provides definitions for user-defined class Point<T>
*/
using System.Collections;
using System.Collections.Generic; // IEnumerable<T>, List<T>
using Analysis;
namespace Points {
/*----------------------------------------------------------------------
Point<T> represents a point in N-dimensional space
- C# generics do not accept integers as generic parameters
so we cannot declare P<T,N> where N is an integer like
we did for C++ and Rust
- Point 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 Point<T> : IEnumerable<T>, Analysis.IShow
{
/*--------------------------------------------------------------------
Constructs an empty point
*/
public Point() {
coor = new List<T>();
}
/*--------------------------------------------------------------------
Constructs a point with N coordinates each with default value
*/
public Point(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 Point class */
public void Show(string name, int _width = 5, int _left = 2) {
int SaveWidth = Width;
int SaveLeft = Left;
Width = _width;
Left = _left;
PrintSelf(name);
Width = SaveWidth;
Left = SaveLeft;
}
/*
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("}");
String callname = " " + dt;
Console.WriteLine(callname);
}
/* 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();
}
/* supports inializer list */
public void Add(T t) {
coor.Add(t);
}
public void AddRange(IEnumerable<T> list) {
coor.AddRange(list);
}
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 Point<T> width(int w) {
this.Width = w;
return this;
}
public Point<T> left(int l) {
this.Left = l;
return this;
}
public Point<T> indent(int i) {
this.Indent = i;
return this;
}
}
}
Point<T>
A reference type parameterized onits 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
1.2.6 Point<T> Demonstration
Display.ShowOp("Point<double>");
Point<double> p = new Point<double>(5);
List<double> tmp = new List<double>{ 1.0, 2.5, 3.0, -2.5, 1.0 };
p.coor = tmp;
Display.ShowTypeEnum(p, "p");
Display.ShowOp("use indexing to retrieve and modify coordinates");
Console.WriteLine(" value of p[1] is {0}", p[1]);
p[1] = -2.5;
Display.ShowOp("using p.show(callName)");
p.Show("p");
Console.WriteLine();
/* uses default constructor with initializer list */
Display.ShowOp("new Point<int> { 1, 2, 3, 4, 5}");
Point<int> pi = new Point<int> { 1, 2, 3, 4, 5 };
pi.Show("pi");
List<int> rng = new List<int> {4,3,2,1};
Display.ShowOp("pi.AddRange(rng)");
pi.AddRange(rng);
int[] arr = { 0, -1, -2 };
pi.AddRange(arr);
pi.width(6);
pi.Show("pi");
Console.WriteLine();
--- Point<double> ---
p: Point<T>, size: 8, reference type
value:
p {
1, 2.5, 3, -2.5, 1
}
--- use indexing to retrieve and modify coordinates ---
value of p[1] is 2.5
--- using p.show(callName) ---
p {
1, -2.5, 2.5, 3, -2.5,
1
}
6/27/2024 2:27:15 PM
--- new Point<int> { 1, 2, 3, 4, 5} ---
pi {
1, 2, 3, 4, 5
}
6/27/2024 2:27:15 PM
--- pi.AddRange(rng) ---
pi {
1, 2, 3, 4, 5,
4, 3, 2, 1, 0,
-1, -2
}
6/27/2024 2:27:15 PM
1.2.7 Generic Functions Definition
/*-------------------------------------------------------------------
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.
1.2.8 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.
1.3 Program Structure
/*-------------------------------------------------------------------
Cs_Generic::Program.cs
- demonstrates creating and using std library generic types:
array, List<T>, Dictionary<K,V>
- demonstrates creating and using user-defined generic types:
Demo<T>, Stats<T>, and Point<T, N>
- depends on PointsGeneric.cs to provide user-defined Point class
- depends on Stats.cs to provide user-defined Stats class
- depends on AnalysisGeneric.cs for several display and analysis
functions
*/
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
using Stats; // defined in Stats.cs
/*-----------------------------------------------
Note:
Find all Bits code, including this in
https://github.com/JimFawcett/Bits
You can clone the repo from this link.
-----------------------------------------------*/
namespace CSharpGenerics
{
/* definition of Hello<T> code elided */
/*-- Generic demonstration begins here --*/
class Program
{
const string nl = "\n";
static void ShowList<T>(List<T> lst) {
/* code elided */
}
static void Demonstrate_Std_Library_Types() {
/* code elided */
}
static void Demonstrate_User_defined_Types() {
/* code elided */
}
static void Demostrate_generic_functions() {
/* code elided */
}
static void Main(string[] args)
{
Display.ShowLabel(" Demonstrate C# generics");
Demonstrate_Std_Library_Types();
Demonstrate_User_defined_Types();
Demostrate_generic_functions();
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 |