about
Bits Objects C#
01/14/2024
0
Bits Repo Code Bits Repo Docs

Bits: C# Objects

library and user-defined classes and objects

Synopsis:

This page demonstrates uses of C# User-Defined types and their objects. The purpose is to quickly acquire some familiarity with user-defined types and their implementations.
  • C# defines a few special class methods: parameterized constructors, and other operators for indexing and comparison etc.
  • The compiler does not generate any of these special methods.
  • Also, this is the first set of examples to partition code into several files. That supports readability, may improve translation times, and makes maintenance significantly easier.
C# Aggregate Type Structure  
Aggregates are types whose instances hold instances of, or handles to, other types, i.e., an array can hold instances of an arbitrary type. Structs and classes can hold named instances or handles to arbitrary other types. There are three ways that happens:
  • Inheritance A C# class can inherit implementation of one base class. The memory footprint of the base class is held entirely within the memory footprint of the derived class. C# classes can inherit multiple interface declarations. That has no effect on the class instance memory footprint.
  • Composition
    When an instance of a C# type holds a value type, the memory footprint of the value type lies entirely within the memory footprint of its parent instance. We say the parent composes its value type element.
  • Aggregation
    When an instance of a C# type holds a handle to a reference type, the parent's memory footprint holds only the handle, not the entire child instance. We say the parent aggregates the type referenced by the handle.
This layout affects how copy operations work:
  • Copies of value types like structs, and tuples create a copy of the source instance at the destination's memory location. That results in two unique instances, the source and the destination.
  • Copies of handles to reference types like arrays, class instances, and objects copy only the handles. That results in two handles referring to the same instance stored in the managed heap.
Demo Notes  
All of the languages covered in this demonstration support classes. Each class provides a pattern for laying out a memory footprint and defines how data within are accessed. Three of the languages: C++, Rust, and C# provide generics. 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. The other two, 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 classes and objects, which for C++, Rust, and C#, are defined in a stackframe or in the heap or both. All data for Python and JavaScript are stored in managed heaps.
The examples below show how to use library and user defined types with emphasis on illustrating syntax and basic operations.

1.0 Source Code

A C# class is a pattern for laying out a type's data in memory with member functions, called methods, for all its operations. Making data members private or providing encapsulated public properties ensures that methods have valid data of their operations. A type designer may choose to selectively allow user data modification.

1.1 PointsObjects.cs

Point4D is a user-defined type representing a point in 4-dimensional space-time. It declares data held by all instances of the type, three floating point coordinates and a DateTime variable, and methods to create and modify instances. The source code is available here. Download the entire Bits repository here.

/*-------------------------------------------------------------------
  Points.cs
  - provides definitions for user-defined class Point4D
*/

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

namespace Points {
  /*-----------------------------------------------------------------------
    Point4D is a basic point class with three integer coordinates and one
    time coordinate.
    - It is a reference type because it's instances are created
      from a class
    - That affects the way assignments work - see demo in Program.cs
  */
  public class Point4D : Analysis.IShow  // reference type with value type members
  {
    public Point4D() {
      /*
        doesn't need to do anything because its properties have
        default values
      */
    }
    public Point4D(double xarg, double yarg, double zarg) {
      x = xarg;
      y = yarg;
      z = zarg;
    }
    public double x { get; set; } = 0.0;
    public double y { get; set; } = 0.0;
    public double z { get; set; } = 0.0;
    public DateTime dt { get; set; } = DateTime.Now;
    public int Length { get; } = 4;
    public int Width { get; set; } = 3;
    public int Left { get; set; } = 2;
    public int Indent { get; set; } = 2;
    /* translates IShow::show to needs of class */
    public void Show(string name) {
      PrintSelf(name);
    }
    /* Display Point4D structure and state */
    public void PrintSelf(string name) {
      string ofstr = Display.Spaces(Left);
      string instr = Display.Spaces(Indent);

      Console.WriteLine("{0}{1} {{", ofstr, name);
      Console.WriteLine(
        "{5}x:{1}, y:{2}, z:{3},\n{5}{4}", name, x, y, z, dt, ofstr + instr
      );
      Console.WriteLine("{0}}}\n", ofstr);
    }
  }
}

Concept:

Point4D represents a point in 3-dimensional space and a time at which something was at that point. Instances of Point4D could be collected into a List<Point4D> to represent the trajectory of a goal kick or flow marker of blood in an artery.

C# class syntax:

C# user-defined types are defined with class implementations. All of the methods and properties must be implemented in line within the class declaration. Point4D inherits interface IShow defined in AnalysisObjects.cs. Any function that accepts input through a parameter of type IShow will accept an instance of any type that implements that interface. Point4D is obligated to implement the Show method declared in IShow. Mutable properties are declared with { get; set; } syntax. get and set are aliases for methods the compiler will implement. Designers can provide definitions where special processing is needed. All of the properties shown here are simply copy-in/copy-out operations so compiler generated code works correctly. Note that the Length property should always return 4, e.g., 3 double coordinates and 1 DateTime value, so it has no setter. Of course, since this is constant the designer might simply choose to eliminate that property. It is included here to show how properties that users should not mutate are defined.

1.2 Source Code - Program.cs

The code block below illustrates this program's structure. Subsequent blocks demonstrate each of the major processing acivities used to present C# object syntax and semantics. The source code is available here. Download the entire Bits repository here.
/*---------------------------------------------------------         
  Cs_Objects::Program.cs
  - Demonstrates creation and assignment of objects as well as a few
    peeks at other operations on objects.
*/
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 CSharpObjects
{
  /*
    Demonstration composite value type.
    - St is a value type because it is implemented with
      a struct and all its data members are value types.
  */
  public struct St {
    public St(int ai, double bi, char ci) {
      a=ai; b=bi; c=ci;
    }
    public void Show(string name) {
      Console.WriteLine(
        "  {0} {{ a:{1}, b:{2}, c:{3} }}", name, a, b, c
      );
    }
    /*
     These data members are public instead of public properties.
     - we don't need that ceremony because St is simply a data
       composer. It doesn't have any significant behavior.
    */
    public int a;
    public double b;
    public char c;
  }

  /*-- Object demonstration begins here --*/
  class Program
  {
    const string nl = "\n";
    
    static void DemoLibraryTypes() {
      /*-- definintion elided --*/
    }
    static void DemoSt() {
      /*-- definition elided --*/
    }
    static void DemoPoint4D() {
      /*-- definition elided --*/
    }
    static void DemoString() {
      /*-- definition elided --*/
    }
    static void Main(string[] args)
    {
      Display.ShowLabel(" Demonstrate C# objects");

      DemoLibraryTypes();
      DemoSt();
      DemoPoint4D();
      DemoString();

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

Structure:

Code begins with declarations of all dependencies needed for program operation. Like most programs, this one begins defining System dependencies. That is followed by declaring dependencies on program defined resources, e.g., the libraies Points and Analysis. Points defines a reference type Point4D and Analysis defines a set of functions for analysis and display of object properties and actions.

Namespaces:

Idiomatic use of C# places a program's definitions in a hierarchal namespace structure used to locate types and operations within a complex set of code facilities. C# uses the dotnet standard libraries to support program operations. These facilities are very large and complex. Namespaces are a valuable resource for finding and using the libraries. The CSharpObjects namespace helps to distinguish program names from the many thousands of names used in included dependencies.

struct St

St is a program-defined value type used to illustrate the semantics of construction and assignment for value types. Because St is a simple data composer its members are all declared public. If the members had complex structures or defined behaviors then they should be declared to be encapsulated properties, as was done for Point4D.

Program Class:

The program defines four functions that illustrate syntax and semantics for: standard library reference types, St value type, Point4D reference type, and immutable string behavior. These are each called in main to illustrate how the various C# types behave. This structure also illustrates how complex code can be partitioned into simpler parts that are easier to understance and maintain that a single monolithic code block.

1.2.1 Demonstrate Library Types

Illustrated here are three of the most important C# data structures: String, List<double>, and Dictionary<int, String>. This block simply creates, initializes, and displays one instance of each. Display functions are documented in the last code block. The source code is available here. Download the entire Bits repository here. The two panels below are separated by a "splitter bar". You can drag the bar horizontally to view hidden code or output.
static void DemoLibraryTypes() {
  Display.ShowNote(
    "Examples of creation and display of Library Types\n  " +
    "- size is the size of reference, not instance",
    "", 60
  );
  /*-- Strings --*/
  string aString = "a string";  // hides construction
  Display.ShowTypeScalar(aString, "aString", nl);

  string another = new string("another string");
  Console.WriteLine("another: \"{0}\"\n", another);

  aString += " and " + another;
  Console.WriteLine("modified aString: \"{0}\"\n", aString);                               

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

  Display.ShowTypeScalar(aList, "aList");
  Console.WriteLine();
  Display.ShowDoubleList("aList", aList, nl);

  aList.Insert(1, -1.5);
  Display.ShowDoubleList("aList", aList, nl);

  /*-- Dictionary --*/
  var d1 = new Dictionary<int, string>
  {
    { 0, "zero"}, {1, "one"}, {2, "two"}
  };

  Display.ShowTypeScalar(d1, "d1");
  Console.WriteLine();
  Display.ShowDictionary("d1", d1, nl);

  d1.Add(3, "three");
  Display.ShowDictionary("d1", d1, nl);
}

------------------------------------------------------------
  Examples of creation and display of Library Types
  - size is the size of reference, not instance
------------------------------------------------------------
aString: String, size: 8, reference type
value: "a string"

another: "another string"

modified aString: "a string and another string"

aList: List, size: 8, reference type
value: "System.Collections.Generic.List`1[System.Double]"

aList: [ 1, 1.5 , 2 , 2.5 , 3 , 3.5 , 4 ]

aList: [ 1, -1.5 , 1.5 , 2 , 2.5 , 3 , 3.5 , 4 ]

d1: Dictionary, size: 8, reference type
value: "System.Collections.Generic.Dictionary`2[System.Int32,System.String]"                        

d1: [ {0,zero} {1,one} {2,two} ]

d1: [ {0,zero} {1,one} {2,two} {3,three} ]














                

1.2.2 Demonstrate Value Type

Value types consist of the primitive types, structs, and enumerations. Assignment and construction from another instance result in copies of the source instance to the destinatin, all in stack memory. The source code is available here. Download the entire Bits repository here.
/*-------------------------------------------------------------------*/                              
static void DemoSt() {
  Display.ShowNote(
    "Example of user-defined value type, St:\n  " +
    "- Value types can be independently assigned and copied.",
    "", 60
  );

  Display.ShowOp("var s1 = new St(1, -0.5, 'z');");
  var s1 = new St(1, -0.5, 'z');

  Display.ShowOp("var s2 = s1");
  var s2 = s1;
  Display.IsSameObj(s2, "s2", s1, "s1");
  s2.Show("s2");
  s1.Show("s1");

  Display.ShowOp("s2.c = 'q'");
  s2.c = 'q';
  s2.Show("s2");
  s1.Show("s1");
  Display.ShowNote(
    "Change in destination, s2, did not alter source, s1.\n  " +
    "Assignment of value types creates independent objects.",
    "\n", 60
  );
}

------------------------------------------------------------                                        
  Example of user-defined value type, St:
  - Value types can be independently assigned and copied.
------------------------------------------------------------
--- var s1 = new St(1, -0.5, 'z'); ---
--- var s2 = s1 ---
s2 is not same object as s1
  s2 { a:1, b:-0.5, c:z }
  s1 { a:1, b:-0.5, c:z }
--- s2.c = 'q' ---
  s2 { a:1, b:-0.5, c:q }
  s1 { a:1, b:-0.5, c:z }
------------------------------------------------------------
  Change in destination, s2, did not alter source, s1.
  Assignment of value types creates independent objects.
------------------------------------------------------------










1.2.3 Demonstrate Reference Type

Reference types consist of handles pointing to instances in the managed heap. Assignment and Construction from source instances makes copies of the handles, but not the instances, from source to destination. That results in two handles pointing to the same heap instance. The source code is available here. Download the entire Bits repository here.
/*-------------------------------------------------------------------*/                              
    /*-- user-defined reference type --*/
    static void DemoPoint4D() {
      Display.ShowNote(
        "Example of user-defined reference type Point4D:\n  " +
        "- a point with x,y,z,t coordinates",
        "", 50
      );
      Point4D p1 = new Point4D();
      p1.x = 3;
      p1.y = -42;
      p1.z = 1;
      p1.Show("p1");
      Display.ShowOp("ShowTypeShowable(p1, \"p1\")");
      Display.ShowTypeShowable(p1, "p1");

      Display.ShowLabel(
        "Differences between value and reference types", "", 55
      );
      Display.ShowNote(
        "Assignment of reference types assigns their references,\n  " +
        "creating two references to the same instance in managed\n  " +
        "heap, so variables are coupled through single instance.",
        "\n", 60
      );

      Display.ShowOp("Point4D val1 = new Point4D(): ref construction", "\n");
      Point4D val1 = new Point4D(1, 2, 3);
      Display.ShowLabeledObject(val1, "val1");
      string addr1 = Anal.ToStringAdddressFromHandle<Point4D>(val1);
      Console.WriteLine("val1 - {0}", addr1);

      Point4D val2 = new Point4D(3, 2, 1);

      Display.ShowOp("Point4D val2 = new Point4D(3, 2, 1)", "\n");
      Display.ShowLabeledObject(val2, "val2");
      string addr2 = Anal.ToStringAdddressFromHandle<Point4D>(val2);
      Console.WriteLine("val2 - {0}", addr2);

      Display.ShowOp("val1 = val2: ref assignment");
      val1 = val2;
      addr1 = Anal.ToStringAdddressFromHandle<Point4D>(val1);
      Console.Write("val1 - {0}", addr1);
      addr2 = Anal.ToStringAdddressFromHandle<Point4D>(val2);
      Console.WriteLine("val2 - {0}", addr2);

      Display.IsSameObj(val2, "val2", val1, "val1");
      Display.println();

      Display.ShowOp("val2.z = 42;");
      val2.z = 42;
      Display.println();

      Display.IsSameObj(val2, "val2", val1, "val1");
      Display.ShowLabeledObject(val2, "val2");
      Display.ShowLabeledObject(val1, "val1");

      Display.ShowNote(
        "Note! Source of assignment, val1, changed when val2\n  " +
        "changed. Point4D is ref type, so assignment just assigns\n  " +
        "references.",
        "", 60
      );
    }










--------------------------------------------------                                                  
  Example of user-defined reference type Point4D:
  - a point with x,y,z,t coordinates
--------------------------------------------------
  p1 {
    x:3, y:-42, z:1,
    1/11/2024 8:12:00 AM
  }

--- ShowTypeShowable(p1, "p1") ---
p1: Point4D, size: 8, reference type
value:
  p1 {
    x:3, y:-42, z:1,
    1/11/2024 8:12:00 AM
  }


-------------------------------------------------------
  Differences between value and reference types
-------------------------------------------------------

------------------------------------------------------------
  Assignment of reference types assigns their references,
  creating two references to the same instance in managed
  heap, so variables are coupled through single instance.
------------------------------------------------------------

--- Point4D val1 = new Point4D(): ref construction ---

  val1 {
    x:1, y:2, z:3,
    1/11/2024 8:12:00 AM
  }

val1 - address: 0x26f1d818520

--- Point4D val2 = new Point4D(3, 2, 1) ---

  val2 {
    x:3, y:2, z:1,
    1/11/2024 8:12:00 AM
  }

val2 - address: 0x26f1d818898

--- val1 = val2: ref assignment ---
val1 - address: 0x26f1d818898
val2 - address: 0x26f1d818898

val2 is same object as val1

--- val2.z = 42; ---

val2 is same object as val1
  val2 {
    x:3, y:2, z:42,
    1/11/2024 8:12:00 AM
  }

  val1 {
    x:3, y:2, z:42,
    1/11/2024 8:12:00 AM
  }

------------------------------------------------------------
  Note! Source of assignment, val1, changed when val2
  changed. Point4D is ref type, so assignment just assigns
  references.
------------------------------------------------------------


1.2.4 Demonstrate String

Strings are immutable reference types. Modification of a string results in a new heap instance of the original string with the specified changes. The source code is available here. Download the entire Bits repository here.
/*-------------------------------------------------------------------*/                              
static void DemoString() {
  Display.ShowLabel(
    "Instances of string are reference types, but simulate\n  " +
    "some value behaviors.  String objects are immutable.\n  " +
    "To modify an instance, a new string is created with\n  " +
    "copies of the source string characters inclucing any\n  " +
    "modifications.", "", 60
  );
  var str1 = "An immutable string";
  var str2 = str1;  // copy handle not instance
  Display.IsSameObj(str2, "str2", str1, "str1");
  Display.DisplayLabeledObject<string>(str1, "str1");
  Display.DisplayLabeledObject(str2, "str2");

  str2 = str2.Remove(0,3);
  Display.IsSameObj(str2, "str2", str1, "str1");
  Display.DisplayLabeledObject(str1, "str1");
  Display.DisplayLabeledObject(str2, "str2");
  Display.ShowNote(
    "There is no way for change in str2 to affect str1.", "", 60
  );
}

------------------------------------------------------------                                        
  Instances of string are reference types, but simulate
  some value behaviors.  String objects are immutable.
  To modify an instance, a new string is created with
  copies of the source string characters inclucing any
  modifications.
------------------------------------------------------------

str2 is same object as str1
str1: An immutable string
str2: An immutable string
str2 is not same object as str1
str1: An immutable string
str2: immutable string
------------------------------------------------------------
  There is no way for change in str2 to affect str1.
------------------------------------------------------------





1.3 Source Code - AnalysisObjects.cs

AnalysisObjects.cs contains functions for analyzing type and properties of objects, and for reducing code clutter for display operations. Several of these are generic and some use reflection. We will focus on those things in the Generic Bit, coming up next. You don't need to understand those functions in full to understand how C# objects work.

/*---------------------------------------------------------
  Cs_Data::AnalysisData.cs
    - class Anal contains static functions that analyze
      types, identities, sizes, and addresses
    - class Display contains static functions that
      provide console displays of headings, notes,
      and operations.
---------------------------------------------------------*/

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Text;
using System.Collections;
using System.Collections.Generic;

namespace Analysis
{
  using Dict = Dictionary<string,int>;
  using KVPair = System.Collections.Generic.KeyValuePair<string,int>;

  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
  }

  class Anal {

    /*-- simple reflection --*/
    public static void ShowType<T>(T t, String nm, String suffix = "")
    {
      /*-- t! asserts that t is not null --*/
      Type tt = t!.GetType();
      Console.WriteLine("{0}: Type: {1}", nm, tt.Name);
      int size = Anal.GetManagedSize(tt);
      Console.WriteLine("value: {0}\nsize: {1}{2}", t, size, suffix);
    }
    /*-- are references equal? --*/
    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);
      }
    }
    /*--
      uses advanced reflection
      - GetMangedSize(Type type) is function that returns the size of
        value types and handles.
        It is 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());
    }
    /*-- returns string displaying heap address of ref type instance t --*/
    public static unsafe string ToStringAdddressFromHandle<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;
    }
    /*-- unsafe cast pointer to address of value type --*/
    #pragma warning disable 8500
    /*
      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.
    */
    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
  }
  class Display
  {
    public static String Spaces(int i) {
      return new String(' ', i);
    }
    public static void ShowOp(String op, String suffix = "") {
      Console.WriteLine("--- {0} ---{1}", op, suffix);
    }
    /*-- more advanced 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
        );
      }
    }
    public static void ShowNote(
      string s, string suffix = "", int length = 35
    ) {
      string line = new string('-', length);
      Console.WriteLine(line);
      Console.WriteLine("  {0}", s);
      Console.WriteLine(line + suffix);
    }
    /*-- Anal.ShowType with additional output --*/
    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 = Anal.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, ...
    */
    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);
    }
    /*-------------------------------------------------
      In Cs_Generic we will show how to implement a
      show function for the enumerable types below.
    */
    public static void ShowIntArray(int[] arr, string suffix="")
    {
      Console.Write("[ {0}", arr[0]);
      for (int i = 1; i < arr.Length; i++)
      {
        Console.Write(", " + arr[i] + " ");
      }
      Console.Write("]\n" + suffix);
    }
    public static void ShowDoubleList(
      string nm, List<double> list, string suffix=""
    ) {
      Console.Write("{0}: [ {1}", nm, list[0]);
      for (int i = 1; i < list.Count; i++)
      {
        Console.Write(", " + list[i] + " ");
      }
      Console.Write("]\n" + suffix);
    }
    public static void ShowDictionary(
      string nm,
      Dictionary<int, string> dict, string suffix=""
    )
    {
      Dictionary<int, string>.KeyCollection keyColl = dict.Keys;
      Console.Write("{0}: [ ", nm);
      foreach(KeyValuePair<int, string> entry in dict)
      {
        Console.Write("{{{0},{1}}} ", entry.Key, entry.Value);
      }
      Console.Write("]\n" + suffix);
    }
    /*-------------------------------------------------
      Surround note with empty lines
    */
    public static void ShowLabel(string s, string suffix="", int len=35) {
      Console.WriteLine();
      ShowNote(s, suffix, len);
      Console.WriteLine();
    }
    /*-------------------------------------------------
      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());
    }
    public static void print(String s = "") {
      Console.Write(s);
    }
    public static void println(String s = "") {
      Console.WriteLine(s);
    }
  }
}

File Structure:

AnalysisObjects.cs defines a namespace "Analysis" which defines an interface for displaying user-defined objects and two classes, "Anal" and "Display" for analyzing object types and properties and for displaying values, respectively.

Interface:

IShow declares one method and four properties that implementors must provide. It guarantees display properties for all user-defined types that implement it. The method ShowTypeShowable<T> can accept any type that implements the IShow interface. This abstraction over behavior allows us to build flexible code that reduces duplication and is easier to understand.

Anal Class:

The method ShowType<T> uses simple reflection to display type information, value, and size. Some quite sophisticated reflection is hidden within the GetManagedSize method it calls. IsSameObj<T> uses System.ReferenceEquals to determine if two handles point to the same instance in the managed heap. It is used to demonstrate the behavior of references when constructed from a source object and when assigned. ToStringAddressFromHandle<T>(T t) evaluates the managed heap address to which t binds. It is used to confirm the results of IsSameObj<T>. ToStringAddress<T>: extracts the address of a value type and returns a string representation of the address. It was used in earlier versions of this code, but is not used now. It is here to illustrate two things: how to find the address of value instances in stack memory and use of unsafe blocks. This function must be declared unsafe because it uses a pointer, but does so in a safe way, e.g., simply returns a string representation.

Display Class:

Most of the functions in Display are easy to understand. Focusing on the less obvious, Iterate<T> uses reflection to display the fields and methods of T. It isn't used in this demo, but is included to help you explore various types used in the RemoteExecution operations. Method ShowTypeShowable<T> illustrates how types are restricted to those that implement IShow with the constraint "where T:Ishow". The methods ShowIntArray, ShowDoubleList, and ShowDictionary all use for-loop iteration to display collection contents. We will show in Generic C# and Iter C# more general methods to display collection contents that accept many different types of arguments by using standard library defined interfaces.

3.0 Build

The left panel shows this project built with the dotnet CLI. The CLI interface has commands: build, run, clean, new, format, ... You can see all of the commands using the command "dotnet -h". The right panel contains XAML code for the project Cs_Objects.csproj. That file was built with the dotnet new command. Note that the project contains an element configuring the build to allow unsafe code. That is needed because one of the analysis functions uses an unsafe block to extract an address from a value type instance. Note that you do not need to, and should not, configure the Visual Studio Code launch file and task file for this code usage. It has to be done in the project file.
Terminal

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

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

Time Elapsed 00:00:00.92
C:\github\JimFawcett\Bits\CSharp\Cs_Objects
>
Cs_Objects.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
  </PropertyGroup>
</Project>






4.0 VS Code View

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

3.0 References

Reference Description
C# Tutorial - w3schools Interactive examples
C# Reference - Microsoft Relatively complete informal language reference