about
Bits Objects C#
01/14/2024
0
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
-
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.
- 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
1.0 Source Code
1.1 Demonstrate Library Types
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 Demonstrate Value Type
/*-------------------------------------------------------------------*/
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.3 PointsObjects.cs
1.3.1 Point4D Reference Type Definition
/*-------------------------------------------------------------------
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:
C# class syntax:
1.3.2 Demonstrate Point4D Reference Type
/*-------------------------------------------------------------------*/
/*-- 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.4 Demonstrate String
/*-------------------------------------------------------------------*/
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.5 Source Code - AnalysisObjects.cs
Analysis and Display Methods
/*---------------------------------------------------------
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:
Interface:
Anal Class:
Display Class:
1.6 Demo Execution - Program.cs
/*---------------------------------------------------------
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:
Namespaces:
struct St
Program Class:
2.0 Build
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>
3.0 VS Code View
4.0 References
Reference | Description |
---|---|
C# Tutorial - w3schools | Interactive examples |
C# Reference - Microsoft | Relatively complete informal language reference |