C# Cheat Sheet

Source: C# 13 and .NET 9 (9th Edition) by Mark J. Price

General

Case Sensitive: yes
Comment: // or /* */
Blocks: {}
End statement: ;
Can break up statements into lines: Yes
PascalCase for Class names and Object instances, Field names, and Function names
Put locally defined functions at the bottom of the file
Ctl-C stops running any code
New Object syntax:
Person bob = new() { Name = "Bob" }; //set fields using equals within brackets
Person bob = new(Name: "Bob"); //when passing fields as arguments use colon

Modifiers

For use before classes, methods, fields
Always explicitly specify for clarity

Class Modifiers
internal — only accessible from within assembly (default)
public — accessible everywhere
private — accessible within same class
file — can only be used within its code file
partial — will be merged with its companion(s)
interface — does not allow instantiation, requires full implementation in classes that inherit it
abstract — not intended to be instantiated, only inherited — can have both implemented and abstract members
sealed — cannot be inherited
record — uses value equality rather than reference equality, cannot be inherited by a class
struct — for small, lightweight simple classes that do not need complex encapsulation, cannot be inherited
static — cannot be inherited or instantiated (use for extending other classes)

Member Modifiers
private — within same class (default)
public — everywhere — derived classes can call as if part of itself
public static — shared by all object instances, don’t need to create object with new to use
protected — within same class or any derived class
internal — within class and any derived class in same assembly
internal protected — within class, any class in same assembly, or any class that inherits it
private protected — within class, any class that inherits it from the same assembly
static — breaks the object wall, don’t need instance to call, call by class name
virtual — allow derived classes to override this
override — this is an override of a base class member marked with virtual
abstract — can only be declared within an abstract class, derived class MUST implement this

Namespaces

In C#, namespaces are like surrounding containers to organize common code. If a code file has a namespace declared, it must be called by its full name when using it, or the namespace loaded at the top by the using keyword. The default namespace is called <Main>$ and is added by the compiler automatically to any partial class that has no namespace. It is then merged with the top-level program class, and can be called directly.

//namespace declared (use semi-colon if only one per file)//otherwise need brackets to enclose everything that belongs

namespace MyCompany.Utilities;
 public class Calculator
  {
     public int Add(int a, int b)
     {
        return a + b;
     }
  }
// In another file in your project, using the namespace to access the Calculator class
using MyCompany.Utils;

// Now you can use Calculator directly
Calculator calc = new Calculator();
int result = calc.Add(5, 3); 
//note no namespace declared
//This file will be merged with top-level program

partial class HelloWorld //merged with <Main>$
{
    static void SayHi() {
     {
         System.Console.WriteLine("Hello World");
    }
}
//Now you can call it directly without the full name or importing it

SayHi();
Namespace Alias

Use aliases for namespaces when 1) there may be a name conflict or 2) to shorten code

//top of file used
using env = System.Environment;
WriteLine(env.OSVersion);
//in project directive
<ItemGroup>
  <Using Include="Namespace.TargetType" Alias="TypeAlias " />
</ItemGroup>
Types
Value types (Stack Types)

Considered equal if the values match
Assignment copies the value into the variable
Lowercase keywords are aliases for types are recognized as .Net types and should be used
Adding ? after any value type variable disables null compiler warnings
Adding ! after a null assignment forgives a non-nullable type


int population; // default 0

double weight; // default 0

decimal price = 5.88M; //default 0 use M suffix

char letter = ‘Z’; //default ‘\0’ use single quotes

bool happy = true; //default false — must be in lowercase

short age; //default 0

long population; //default 0

DateTime birthdate = new(1960, 1, 31); //must use new to set with numbers

DateTime birthday = DateTime.Parse(“Jan 1, 1959”); //use parse to set with string

Point location = new (10, 20); //must use new to set x and y coords

struct or record struct // located in stack unless has heap types within it, default constructor generated if not there

Reference types (Heap Types)

Address in stack, value put in heap on new(). All default values are null
Considered equal if have the same heap address (exception: string)
Assignment copies the address into the variable
Adding ? after any reference type variable changes type to Nullable<T>

collections (see below)

objects
Person bob; //address put into stack

bob = new(); //now default Person is created in heap at that address

record

string //string is exception to equality rule (the value is compared)

string fruit = "apple"; //new() is optional on strings — use double quotes — default is null

string? myString; //becomes Nullable<string> — disables compiler warnings

Var Type

Using var as type, the compiler will set it at compile time.
Use var when it’s obvious to a human reader what the type would be.
Compiler can also infer from new() back to the data type.

var population = 67.5; //compiler knows you want a double.

var xml1 = new XmlDocument(); //compiler makes space for XmlDocument

XmlDocument xml1 = new(); //compiler also knows

Dynamic Type

Set at runtime, when an actual value is assigned, and can be changed.

dynamic returnResult; //compiler doesn’t know what you want.

returnResult = true; //oh ok it’s a boolean now

returnResult = 3.88M; //now it’s a decimal

NOTE: Can set any variable back to default with keyword default
fruit = default; //now is null

Nulls

Avoid NullReferenceException at runtime by checking for nulls before using variables

//use is or is not instead of == or !=
if (someVar is null) { }
if (someVar is not null) { }


//instead of throwing Exception, assign null
int? authorNameLength = authorName?.Length;

//assign a specific value if null
int authorNameLength = authorName?.Length ?? 25;

//check for nulls in method parameters using !!
public void Hire(Person manager!!, Person employee!!) { }
— now will automatically throw ArgumentNullException if either of these args are null

Strings and Chars

string line = new string('_', 52); — 2nd arg is number of times to repeat char (note single quotes)

string? input = Console.ReadLine(); — tell compiler that a null value is possible, so don’t warn

string1 + string2; — quick concat

Verbatim

string myString = @"taken exactly as it is /even with backslashes (but not double quotes)";

Interpolation (sticking stuff inside a string)

string myString = Write($"{const1} is a {const2}"); //variable names use $

string myString = Write("{0} is a {1}", arg0: "Cat", arg1: "animal"); //passing args no $

Formatting

string myString = string.Format("{0} is {2}", "A rose", "a rose");
{0, -10} — to format arg0 with min of 10 spaces, aligned left

myString.Trim (TrimStart, TrimEnd) — removes whitespace, tab, carriage returns from start/end or both

myString.ToUpper (ToLower) — converts to upper or lowercase

String Functions

all return value (do not alter original string)
some methods are static and are called with string.FunctionName (your string is passed as arg)

Returns Boolean (tests the string)

string.IsNullOrEmpty(myString)

string.IsNullOrWhiteSpace(myString)

myString.StartsWith('M') — or EndsWith (can be char or string)

myString.Contains('n')

Returns a string

string.Empty — use to instantiate an empty string – returns “”

myString.Length — returns integer

myString[3] — strings are stored as arrays of characters so this works (0 based)

string[] stringArray = myString.Split(','); — to split up a string into an array at delimiter

myString = stringArray.Join(' '); — to join array into string with separator

myString.Substring(startIndex) — start here and return to end

string.Concat(string1, string2); — (note: for building long strings see StringBuilder below)

myString.Substring(startIndex, length) — start here and return this many

myString.Remove(startIndex, count) — start here and remove this many

mystring.Insert(startIndex, whatToInsert) — insert this here

myString.IndexOf('n') — first index of whatever, can optionally pass in where to start

myString.Replace(old, new) — can replace char or string, returns new string

Char Functions

char.IsDigit(myChar) or char.IsDigit(mystring, atPlaceNum)

also: IsLetter, IsUpper, isLower, IsSymbol, IsWhiteSpace, IsSeparator (tab, space, newline)

Comparing Strings (sorting)

string.Compare(string1, string2) — does string1 go before (-1), after (1), or equal to string2

string.Compare(string1, string2, ignoreCase: true)

string.Compare(text1, text2, StringComparison.InvariantCultureIgnoreCase) — language specific

Searching Within Strings & RegEx

myString.IndexOf('n') — index where whatever char or string is

myString.Contains('n') — contains whatever returns boolean

Using SearchValues type
string[] names = ["Cassian", "Luten", "Mon Mothma", "Dendra", "Syril", "Kino"];

//create SearchValues type of whatever you're looking for
SearchValues<string> searchFor = SearchValues.Create(names, StringComparison.OrdinalIgnoreCase);

string whatToSearch = "In Andor, Diego Luna returns as the titular character, Cassian Andor, to whom audiences were first introduced in Rogue One.";  //or can use ReadOnlySpan<string>

//returns the index if anything in searchValues exists, else -1
int found = whatToSearch.IndexOfAny(searchFor);
Using RegEx

Good to validate user input

Using System.Text.RegularExpressions;   //to use RegEx

//set your pattern in RegEx type
RegEx ageRE = new(@"^\d$");  //here just digit -- need @ to allow backslashes

//check whatever against pattern
bool doesItMatch = ageRE.IsMatch(whatever);
SymbolMeaningSymbolMeaning
^At the start of input$At the end of input
+One or more (of prev)?One or none (of prev)
{3}Exactly three (of prev){3, 5}Three to five (of prev)
{3,}At least three (of prev){,3}Up to three (of prev)
\dA single digit\DA single non-digit
\sA space\SA non-space
\wA word\WA non-word character
[A-Za-z0-9]In a range of characters\^A caret
[aeiou]In a set of characters[^aeiou]Not in a set of characters
.Any single character\.Dot character

Find more at https://regex101.com

String Builder

This takes much less memory than concat for very long strings or where you build in a loop

System.Text.StringBuilder builder = new();

    //build your string with whatever code....
    for (int i = 0; i < numbers.Length; i++)
    {
      builder.Append(numbers[i]);
      builder.Append(", ");
    }

    return builder.ToString();
Extending the String class
public static class MyStringExtensions   //make your class static
{
    public static bool IsValidEmail(this string input)  //use the this keyword before parameter
{ 
...

//now can use it in your code
string email1 = "terrymarr@test.comm";
bool isValid = email1.IsValidEmail();   //works like a method on base string class
Numbers

:N0 —- format a number with thousands separator and zero decimals

:C — format a number as currency in the current culture

:B8 — format a number in binary with 8 digits

:B32 — format as binary padded with leading zeros to a width of 32

:X2 — format as Hexidecimal

TypeByte(s)MinMax
sbyte1-128127
byte10255
short2-3276832767
ushort2065535
int 4-21474836482147483647
uint 404294967295
long 8-9223372036854775808 9223372036854775807
ulong8018446744073709551615
Int12816-170141183460469231731687303715884105728 170141183460469231731687303715884105727
UInt128160340282366920938463463374607431768211455
Half 2-65500 65500
float 4-3.4028235E+383.4028235E+38
double 8-1.7976931348623157E+3081.7976931348623157E+308
decimal16-7922816251426433759354395033579228162514264337593543950335

NOTE: sizeof(type) for capitalized types (Int128, UInt128, Half) only works in unsafe code blocks

In System.Numerics
TypeNote
BigIntegerreally large integers256 MB (646.5 mill digits)
Complexa + bi —
i is always √(-1)
new(real: 4, imaginary: 2);‘imaginary’ means what to be
multiplied by √(-1)
Weird Number Stuff
ExpressionValue
double.NaNNaN
double.PositiveInfinity
double.NegativeInfinity-∞
int / 0DivideByZeroException
0.0 / 0.0NaN
3.0 / 0.0
-3.0 / 0.0-∞
3.0 / 0.0 == double.PositiveInfinityTrue
-3.0 / 0.0 == double.NegativeInfinityTrue
0.0 / 3.00
0.0 / -3.0-0

IMPORTANT!! Never compare two doubles using ==. (Using greater/lesser than is ok.)

0.1+0.2 (doubles)does NOT equal 0.3!!!
0.1+0.2 (doubles)equals 0.30000000000000004
0.1M+0.2M (decimals)equals 0.3

Use decimals for money, engineering and for values that will be compared equally

Other Number Stuff

Use Math.BigMul(number1, number2) method when multiplying large integers to avoid overflow. Will return value in next largest type.

DateTime

using System.Globalization; //set Globalization at top

CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("en-US"); //or whatever

DateTime birthday = DateTime.Parse("6 June 1978"); //Parse converts string to DateTime

DateTime nye = new(2011, 12, 31); //must use new() if setting with numbers

Standard Date Formatting

WriteLine($"{birthday:D}"); //with a colon

FormatReturns (en-US)
:​DMonday, January 15, 2009
:d6/15/2009
:fMonday, January 15, 2009 12:00 AM
:FMonday, January 15, 2009 12:00:34 AM
:g6/15/2009 1:45 PM
:G6/15/2009 1:45:30 PM
:M or :mJanuary 15
:Y or :yJanuary 2009
:​0 or :​o2009-06-15T13:45:30
Arrays

Use arrays when the number of elements is fixed, e.g. a list of states.

string[] pacific; // declared but size not fixed yet — (any datatype)

string?[] pacific; //declare and can have null values

pacific = new string[3]; //size is fixed

byte[] arrayOfBytes = new byte[256]; //declare array of bytes with 256 elements

string[] states = {"Washington", "Oregon", "California"}; //declared and fixed

string[] states = ["Washington", "Oregon", "California"]; //also works (single-dimension)

states[0] = "Hawaii"; //set value by position

string[,] grid1; //declare 2-dimensional array

string[3,4] grid1; //declare and set number of elements in each dimension

string [][] jaggedarr; //array of arrays (each array can have different sizes (jagged))

arrayName.GetUpperBound(0) // the last position in the (first) array

String.Join(‘,’, myArray); //joins arrays into one string for output

int[] = combinedRows = [..arr0, ..arr1, ..arr2]; //combine different arrays using spread

Index i4 = new(value: 3) //Index type allows to count from start or end

Index i3 = new(value: 5, fromEnd: true); //count in 5 from end (start with 1)

Index i3 = ^5; //count from end (start with ^1) (^0 will error)

Range r1 = ..3; //returns positions 0, 1, 2

Range r2 = 3..; //returns position 3 to end

foreach (int i in arrOfInts[..3]) //or can pass in preset Range

Span<T> //span represents contiguous subelements (a range) of an array

ReadOnlySpan<int> mySpan = myArray[3..]; //from 3rd position to end

Collections

Always use generics that force data typing within a collection

From System.Collections.Generic

Or System.Collections.Immutable //if want to use immutable collections

All Collections implement:

myColl.EnsureCapacity(10,000); //set size to ensure # of elements (makes faster)

myColl.Count; //integer

myColl.IsReadOnly; //bool

myColl.CopyTo(myArray, intIndex);

foreach

myColl.Add(T item);

myColl.Clear();

myColl.Contains(T item); //bool

myColl.Remove(T item);

myColl.AsReadOnly() //make immutable

myColl //spread element (two dots)

NOTE: Immutable collections aren’t alterable, but will create an altered coll or throw ex

Lists

List<T> — T can be any type

LinkedList<T> //keeps track of prev and next in list

ImmutableList<T> //from Immutable namespace

List<string> myList = new(); //declare

List<string myList = new() {"London", "New York", "Los Angeles"}; //declare with array

List<string myList = ["London", "New York", "Los Angeles"; //also works

myList.AddRange(new[] {"London", "New York"}); //pass in new array or array var

myList.Add("Paris"); //add item using Add method

myList[3] — (to get item positional)

myList.Sort(); //indexes will change

myList.Reverse();

myList.IndexOf(T item);

myList.Insert(intIndex, T item);

myList.RemoveAt(intIndex);

myList.TrueForAll(item => item.Contains(‘e’))

Dictionaries

Dictionary<TKey, TValue> //both key and value can be any type

ImmutableDictionary<TKey, TValue> //from immutable

SortedList<TKey, TValue> //suitable for small, static collections that are already in order

SortedDictionary<TKey, TValue> //suitable for large, dynamic collections (more overhead)

Dictionary<string, string> myDic = new(); //declare

Dictionary<string, string> myDic = new() {{key1, val1, key2, val2}} //declare and instantiate

Dictionary<string, string> myDic = new() {[key1] = val1, [key2] = val2} //declare and instantiate

To Add Values:

myDic.Add(newkey, newvalue) //key first

myDic.Add(key: newkey, value: newvalue); //specify parameters

To Get Values:

myDic.Keys — also a collection

myDic.Values — also a collection

myDic[“keyname”]

eachMyDicElement.Value — get value

eachMyDicElement.Key — get key

Sets

Useful when comparing two collections

HashSet<string> mySet = new(); //declare

SortedSet<string> mySortedSet = new(); //keeps items in order, must be unique

ReadOnlySet<string> //read only

mySet.Add(newItem); //adds only if not already there – returns boolean

mySet.ExceptWith(anotherSet); //show only items in first set that aren’t in second

mySet.IntersectWith(anotherSet); //show only the items that are in both sets

mySet.IsSubsetOf(anotherSet); //all items are in other set

mySet.IsProperSubsetOf(anotherSet); //all items are in another set AND other set has 1+ items not in this one

mySet.IsSupersetOf(anotherSet); //all items exist in other set

mySet.IsProperSupersetOf(anotherSet); //all items exist in other and AND this set has 1+ items not in other set

mySet.Overlaps(anotherSet); //share at least 1 item

mySet.Equals(anotherSet); //share all

mySet.SymmetricExceptWith(anotherSet); //makes mySet equal to passed in set

mySet.UnionWith(anotherSet); //adds anything in another set not in this one

Stacks

Last in – First Out (LIFO)
Can only access, add or remove from top of stack
Good for keeping track of events
Cannot be sorted

Stack<string> myStack = new Stack(); //declare

Stack<string> stack2 = new Stack<string>(myStack.ToArray()); //declare and load w array

myStack.Push(“Hello”); //push onto top

myStack.Peek(); //peek at top of stack

myStack.Pop(); //remove from top of stack

Queues

First in- First Out (FIFO)
Can only access oldest item
Cannot be sorted

Queue<string> myQ= new(); //declare regular queue

PriorityQueue<string, int> myPQ = new(); //declare with priority levels

myQ.Enqueue(“New item”); //add to regular queue

myPQ.Enqueue(“New Item”, 3); //add with priority

myQ.Peek(); //peek at next in queue

myPQ.Peek(); //peek at next by priority

myQ.Dequeue(); //remove next

myPQ.Dequeue(); //remove next by priority

myPQ.Remove(“nameToRemove”); //removes first one it finds, regardless of priority

myPQ.UnorderedItems; //return list regardless of priority

Enums

For fixed list of values

Int by default but can choose to inherit other integer types

Use byte if 8 values or less

Use ushort up to 16 values

Use uint up to 32 values

Use ulong up to 64 values

Use [Flags] decorator to allow multiple values

[Flags]
public enum DaysOfTheWeek : byte {
 Sunday = 0b_0000_0000, //0
 Monday = 0b_0000_0001,  //1
 Tuesday  0b_0000_0010,  //2
 Wednesday = 0b_0000_0100,  //4
 Thursday = 0b_0000_1000, //8
 Friday = 0b_0001_0000, //16
 Saturday = 0b_0010_0000 //32
};
//now can use it like this:
bob.CleaningSchedule = DaysOfTheWeek.Tuesday | DaysOfTheWeek.Friday;

Tuples

When want to create a variable that has more than one value attached without creating new class
var myTuple = ("This is a string", 14); => myTuple.Item1 and myTuple.Item2

Referenced as Item1, Item2 unless named like so:
var myTuple = (Name: "Terry", Grade: "A"); //capitalize field names

Or inferred from another object’s fields
var bobGrades = (bob.English, bob.Spanish); => bobGrades.English and bobGrades.Spanish etc.

Can return tuple from a method:
public (string Name, int Age) GetNameAndAge()
{
return (Name: "Terry", Age: 30);
}


Can alias a tuple type at the top
using Fruit = (string Name, string AvailableMonth);
then instead of var can use your alias:
Fruit apples = (Name: "Fuji", AvailableMonth: "April");

Can deconstruct a tuple into its component parts
(string name, int age) = terry.GetNameAndAge(); => now name and age variables have values

Operators
Null-coalescing Operators

int maxLength = authorName?.Length ?? 30; — use the length unless it’s null then 30
authorName ??= “unknown”; — if null make it “unknown”

Logical Operators — work when operands evaluate boolean

& (AND)— both operands are true
| (OR) — either operand is true
^ (Exclusive OR) — only if operands are different
&& (short-circuit) — if first operand is false, skip evaluating the second operand

Bitwise Operators — comparing bits — works when operands are integers

Ints are read as bits, then operator is used so 10 & 8 = 8
10 & 6 = 6 — 0001010 & 00000110 — returns 00000010 (set the column where both are true)
10 | 6 = 14 — 0001010 | 00000110 — returns 00001110 (set the column where either are true)
10 ^ 6 = 12 — 0001010 ^ 00000110 — returns 00001100 (set the column where only one is 1)
1 << 2 = 4 — 00000001 shifted to left 2 places = 00000100 = 4

Misc Operators

nameof(myVar) — name of variable
sizeof(myVar) — size in bytes

Branching
if else
if (boolean value or statement)
{   //always use braces
  do something if true
}
else
{
  do something if false
}
switch
switch (someVariable)
{
  case val1: 
    //DO SOMETHING HERE
    break;   //jump out of switch
  case val2:
    //DO SOMETHNG ELSE
    goto case 1; //jump to another case
  case val3:
  case val4:
    //3 FALLS INTO 4
    break;
  case val5:
    goto a_label;      //jump to label
  Default:
    //IF ALL ELSE FAIL WILL COME HERE
    break;
} //end of switch

a_label:  //jump label use sparingly if at all
//some code here;
Switch shorthand

Separate cases with comma (trailing on last one ok)
Omit case keyword and colon
_ denotes default case
Use => to return value from case, can be on new line
break; not needed

string message = animal switch
{
  Cat c when c.Legs == 4 => "4 legs",   
  Cat c when c.Legs == 3 => "3 legs", 
  Cat c when c.IsDomestic == false => "Wildcat",
  null => "The animal is null",
  _ => $"{animal.Name} is a {animal.GetType().Name}"
};  
Switch Case Pattern Matching

Can also check type of the variable in the case:
case Cat cat:
Or check type & property of a class variable in the case:
case Cat c when c.Legs == 4:
using shorthand
Cat c when c.Legs == 4 => "4 legs",
or even shorter (C#9 or later)
Cat {Legs: == 4} => "4 legs",

Loops
while
while (x < 10)
{  do something   x++;
}
do while
do
{
  do something
  x++;
}
while (x < 10)
for
for (int y = 1; y < 10; y++)
{
   do something
}
foreach
foreach (string name in names)
{
   WriteLine($"{name}");   //property values cannot be modified
}
Type Converting & Rounding

Cast between similar types
Convert between different types

Implicit Casting

setting one type equal to another similar type

only works if data won’t be lost (make bigger not smaller)

can go up to parent class, but not down to more specific

int a = 10;

double b = a; //ok because double is bigger than int

Person alice = EmployeeAlice; //casted UP — Employee is derived from Person

Employee alice = PersonAlice; //won’t compile — cannot cast DOWN (must be explicit)

Explicit Casting

double c = 9.8;

int d = c; //won’t compile because int is smaller than double

int d = (int)c; //explicitly cast into an int (warning – will truncate)

Casting safely

if (personAlice is Employee)
{ 
    Employee explicitAlice = (Employee)personAlice;  //now can safely cast
}

OR
if (personAlice is not Employee)
... 

OR 
Employee? explicitAlice  = personAlice as Employee; //will be null if not cast
if (explicitAlice is not null) //now check for null
...
System.Convert

Use by importing System.Convert class
Will error if it cannot convert

ToInt32(9.8); // rounds up to 10

System.Convert Rounding Rules (Banker’s Rounding)

Toward zero when decimal < .5
Away from zero when decimal > .5
Away from zero when decimal == .5 and integer part is odd
Toward zero when decimal == .5 and integer part is even

Rules are defined in these constants:
MidpointRounding.AwayFromZero
MidpointRounding.ToZero
MidpointRounding.ToEven
MidpointRounding.ToPositiveInfinity
MidpointRounding.ToNegativeInfinity

Use them with Math.Round:
Math.Round(value, digits, mode)
Math.Round(9.5, 0, MidpointRounding.AwayFromZero) returns 10

AwayFromZero 9.5 becomes 10
ToZero 9.5 becomes 9
ToPositiveInfinity 9.5 becomes 10
ToNegativeInfinity 9.5 becomes 9
ToEven 9.5 becomes 10
ToEven 10.5 becomes 10

Converting To String

yourVariable.ToString()

dateTimeVar.ToString("mm/dd/yyyy"); //format you want

don’t have to call ToString when passing to WriteLine method

Safe conversion from binary to base64 string (for transmission)

string encoded = ToBase64String(byte[]); — takes array of bytes

string encoded = Base64Url.EncodeToString(ReadOnlySpan<byte>) –notice what it takes

Using TryParse method

Use TryParse method to avoid errors

//returns true or false, 
//out is the number if parseable

if (int.TryParse(input, out int result)) 
{
  intToStore = result;  
} else {
  //do something if not parseable
}
Classes (Custom Types)

Class is the blueprint or type, Object is the instance
Build your code using classes
Put in a logically defined namespace (eg: MyCompany.Utilities)
Each class should be in its own file, can split into partial classes
Class objects are only equal if they have the same memory address (exception: Record classes)
Record objects are equal if all the properties match

Inheritance

public class subclass/derived : superclass/base

Derived class IS A subtype of the base class (Car is a type of Vehicle)

Inheritance implies getting functionality for free from superclass which you can usually override.

A class can only inherit from one other class, but can implement many interfaces (separate with comma)

Exception: all objects automatically inherit from System.Object

If a base method is marked virtual (Go to definition) then you can override it

Constructors are not inherited. You must explicitly define and call them using base keyword.

System.Object

Available methods/properties:

objA.ToString() => namespace.className

objA.GetType() => namespace.className

objA.Equals(objB) => True if both point to the same reference

objA.GetHashCode() => Unique ID of this object

Record Class

Special type of class where the whole object is immutable after instantiation

Copy of a record object is considered equal if all the properties match

Ideal for holding records of data that won’t change after loading

public record class ImmutableVehicle   //best practice to use record and class keywords
{
     public int Color {get; set;}  //this makes this field still changeable
}

Now every time a change is made to the property, an entirely new object is copied into memory.

ImmutableVehicle car = new() {Color: "Gray"}; — original object

ImmutableVehicle repaintedCar = car with { Color = "Red" }; — copy — note keyword with to change prop

//simpler syntax to auto-generate constructor, deconstructor and properties
public record class ImmutableVehicle (string Color, string Make);

Now can construct like this:
ImmutableVehicle car = new("Blue", "Mazda");

And deconstruct:
var (color, make) = car;

Note: To make a record class unchangeable after instantiation use a constructor signature
public record class Unchangeable(string? Name);

Struct instead of Class

Use for simple stack-memory type, treats an object like a primitive value
A default (parameterless) constructor will be generated if not defined

  • When total stack memory of all members is 16 bytes or less
  • When all members are value types
  • When it won’t be inherited

public struct
not immutable unless you add readonly keyword
can use .Equals to compare to instances (but not ==)

public record struct
immutable (creates copy of values)
can use .Equals or == to compare instances

Interfaces

An interface is a special class with method signatures for some expected functionality.

Any type that implements an interface promises that those methods will work on this type.

This class CAN DO these things.

A class can implement multiple interfaces.

Not to be used to instantiate objects, only to add functionality.

Every member of an interface must be public (or internal if only to be used in same assembly).

By default all members are abstract and public.

Some members may have default implementation (C# 8 or higher) which can be overridden.

Interfaces can have properties or methods, but not fields.

Any class that implements an interface or abstract class will not compile or instantiate unless fully implemented.

interface IAnimal
{
    void animalSound();  //signature only, does not have implementation
    void run();          //both these members MUST be implemented by any class that uses it
}
class Pig : IAnimal
{
   //override the two methods in the interface
   public void animalSound()   //Note don't need override keyword
    {
       WriteLine("wee wee wee");
    }
   public void run()   //must return same type as interface
   {
      WriteLine("The pig is running");
   }
}

Commonly used interfaces:

InterfaceMethod(s)Description
IComparableCompareTo(other)To order or sort instances of a type
IComparerCompare(first, second)To order or sort instances of a primary type
IDisposableDispose()To release unmanaged resources
IFormattableToString(format, culture)To format in a culture-aware way the value of an object
IFormatterSerialize(stream, object)
Deserialize(stream)
To convert an object to and from a byte stream for transfer or storage
IFormatProviderGetFormat(type)To format inputs based on language and regionn
Fields/Properties

Used to store data about the object — together called state

Technically a Property is a method — runs with get/set — see below

Fields marked private are used with get/set — prefix with _underscore.

Fields marked static are shared across all instances

Fields marked const are set when declared/compiled and never change

To retrieve both static and const fields use class name, not instance name

Fields marked readonly are a better choice for non-changeable values,
– can be loaded at runtime and are attached to instance variables

Fields marked required must be given values (even null) at instantiation

Methods

Class functions that execute statements — with or without return value

public static — don’t need an instance of an object to be usable

public void MyMethod() — no result expected

public string MyMethod() — string expected with a return statement

Parameters

public string MyMethod(int namedParam) — required – value copy only exists inside method

public string MyMethod(int namedParam = 100) — optional parameter (put at end)

public string MyMethod(in int namedParam) in cannot be changed inside the method

public string MyMethod(out int namedParam)out must be set inside the method
– pass the receiving variable into method with out keyword myObj.MyMethod(out someInt);
– or can declare when passing myObj.MyMethod(out int someInt);

public string MyMethod(ref int namedParam)ref in-out, doesn’t have to be set in the method
– pass variable into method with ref keyword myObj.MyMethod(ref someInt);

Overloading

Same name of method but using unique signatures (list of parameters with different types)

Extension Methods

In a static class, a method that extends the functionality of a passed in parameter (another class), adds a new method to it without altered the original class.

public static class CarExtensions  //make your class static
{
   //your method can return whatever you like, or alter and return original object
   public static bool IsRegistered(this Car car)  //use the this keyword before parameter
{ 
...

//now can use it in your code
Car myCar= new Car();
bool registered = myCar.IsRegistered();   //works like a method on base Car class
Special Method Categories
Constructor

Called when object with new keyword

//basic
public Book() {}

//or can set default values for fields
//including readonly fields
public Book()
{
    CreatedOn = DateTime.Now;
}

//with parameters to force the setting of required fields
//uses decorator from System.Diagnostics.CodeAnalysis 
[SetsRequiredMembers]  
public Book(string? isbn, string? title) : this() //inherit from default constructor
{
   Isbn = isbn;  //set as required in field definition
   Title = title;  //required
}

//overloaded constructor uses different signature (params with different types)
[SetsRequiredMembers]
public Book(string? isbn, string? title, int pages) : this()
{
   Isbn = isbn;  //set as required in field definition
   Title = title;  //required
   PageCount = pages;
}

Primary Constructor — defined with class

//positional params are a part of class definition (can only define one)
public class Headset(string manufacturer, string productName) //put right at the top with class
{
    public string Manufacturer { get; set; } = manufacturer;  //still need to set
    public string ProductName { get; set; } = productName;
}

Create new object using positional args: Headset bose = new Headset("Bose", "AudioMax");

Default Parameterless Constructor

//calls primary constructor to set default values
public Headset() : this("Microsoft", "HoloLens") {};  //note it inherits from itself with this

NOTE: Primary Constructors are controversial. Better to define constructors separately.

Property (Getter/Setter)

Called when getting or setting data
readonly (only has a get method) public string Age => birthYear - DateTime.Now.Year;
readwrite (must have both get & set) public string? FavoriteMovie {get; set;}
init-only cannot change after initialized (replace set with init)
with private field:

private string? _favoriteIceCream; //place to store

public string? FavoriteIceCream
{
     get
      {
        return _favoriteIceCream;
      }
     set
      {
       //do something here then 
       _favoriteIceCream = value;   //note implicit keyword 'value' (what is being assigned)
      }
}
Deconstructor

Use with tuples to deconstruct an object into its parts

public void Deconstruct(out string? name, out DateTime dob)
{
     name = Name;
     dob = Born;
}

//can overload with different signatures
public void Deconstruct(out string? name, out DateTime dob, out decimal gpa)
{
     name = Name;
     dob = Born;
     gpa = GradePointAverage
}
Indexer

Uses array[] syntax, when you want to get/set an element in a collection

public Person this[int index]   //note this[] is used to define an indexer signature, 
                                // can be any type but usually int
{
   get
   {
      return Children[index];  //returns the item from the collection
   }
    set
    {
      Children[index] = value;   //sets the item in the collection
    }
}

Now you can get using index syntax
Person child = bob[0]; //indexer gets Child object at position 0
bob[0] = newbabyObject; //indexer set – overwrites the 0 object in collection
bob["Clayton"] = newbabyObject; //can use string too (will need to alter code above)

Operator

When you want to use an operator like + – / etc. on your class

public static bool operator +(Person p1, Person p2)
{
     Marry(p1, p2);                     <---- call a method you've already defined
     return p1.Married && p2.Married;   <---- cannot return void
}
Guard Clause Methods — to check if args are valid

ThrowIfNullOrEmpty
ThrowIfNullOrWhiteSpace
ThrowIfNull
ThrowIfEqual
ThrowIfGreaterThan
ThrowIfGreaterThanOrEqual
ThrowIfLessThan
ThrowIfLessThanOrEqual
ThrowIfNegative
ThrowIfNegativeOrZero
ThrowIfNotEqual
ThrowIfZero
Argument.ThrowIfNullOrWhiteSpace(inputArgName, paramName: nameof(inputArgName));

Documenting Your Methods

Add three slashes above method declaration to add documentation to all callable functions

/// <summary>
/// Pass a 32-bit unsigned integer and it will be converted into its ordinal equivalent.
/// </summary>
/// <param name="number">Number as a cardinal value e.g. 1, 2, 3, and so on.</param>
/// <returns>Number as an ordinal value e.g. 1st, 2nd, 3rd, and so on.</returns>
static string CardinalToOrdinal(uint number)
...
Partial Methods

A way to delcare a placeholder function within a partial class and allow other developers to implement it how they want in the merged partial class.

within first partial class file:
partial void placeholderFunction(); //notice no implementation
first partial class can call it but
it will be ignored until an implementation is merged into it

within second partial class file:
partial void placeholderFunction()
{ //something happens here }


Both must be inside partial class. Both must return void. Both must say partial. No out params.

Delegates and Events

Delegate holds the signature of a method
Used to pass a method as an argument to other methods
Subscriber: OK when X happens, here’s what I want you to do
To exchange messages between objects
To handle events in a flexible way
Delegate acts as the go-between

Predefined Delegates

MS-defined delegate types that you should use
public delegate void EventHandler(object? sender, EventArgs e);
OR
public delegate void EventHandler<TEventArgs>(object? sender, TEventArgs e);

where
delegate — keyword to declare the delegate
EventHandler — the name of the delegate type (MS defined this)
sender — the object that triggers the event and will be notified when finished
EventArgs — an array of arguments for whatever method will be called
TEventArgs — for multiple types of args

Declare event (a method) in your class using predefined delegate:
public event EventHandler? OnPokeEvent; //starts as null

Link the method in your class that will trigger the event
public void Poke
{

//triggers OnPoke event
OnPokeEvent(this, EventArgs.Empty);
}


Write a handler method in a Program.EventHandler partial file:
public void HandlePoke (object? sender, EventArgs e)
{
WriteLine("Stop it!"); //what actually happens
}


In Object instance subscribe the handler to the event
harry.OnPokeEvent += HandlePoke;

Now when harry object runs Poke() he will react to it. Other objects can also subscribe to it and pass the same handler, or a different one.

Console Apps

using static System.Console; — either at top of code file
or in project directive file like this:

<ItemGroup Label="make console static">
  <Using Include="System.Console" static="true" />
</ItemGroup>

Write("something"); — writes without carriage return
WriteLine("something"); — adds carriage return

Detecting Key Presses

Write("Press key: "); — user prompt
ConsoleKeyInfo key = ReadKey(); — waits for user input
key.Key — the key’s id to computer
key.KeyChar — the translation of key pressed (what shows on screen)
key.Modifiers — Shift, Control, Alt.

Run Arguments

MyProgram arg1, arg2, arg3

Arguments are an array of strings, always passed at top level program, though hidden
Test in Project > Arguments Properties > Debug > General > Open Debug Launch Profiles UI

Different Operating Systems

Helpful methods in System class to detect the OS:

OperatingSystem.IsWindowsVersionAtLeast(major: 10))

OperatingSystem.IsWindows()

OperatingSystem.IsIOSVersionAtLeast(major: 14, minor: 5))

OperatingSystem.IsBrowser()

Compile Directives

Using predefined constants to only compile certain code for specific OS:

#if NET7_0_ANDROID
  //statements that will only work for Android
#elif NET7_0_IOS
  //statements that will only work for IOS
#else
  //statements For everything else
#endif

See list of OS constants here https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives

Exceptions

Avoid over-catching exceptions
catch (FormatException) //catch specific
catch (Exception ex) //catch all else – declare System.Exception variable
ex.GetType() //eg System.FormatException
ex.Message //The default message of the exception
catch (FormatException when (amount.Contains(‘$’)) //use a filter

//to check for overflow exceptions
try
{
  checked   //avoids silent overflow
  {
    int x = int.MaxValue;
    x++;
  }
}
catch (OverflowException)
{
    WriteLine("Caught!");
}
Throwing

throw; //rethrows keeping original call stack
throw ex; //rethrow at the current level in call stack, will lose potential helpful call stack info
throw new InvalidOperationException(
message: “Calculation had invalid values. See inner exception for why.”,
innerException: ex);
//to add helpful info

Defining Custom Exceptions

Define your own exception class inheriting from Exception class and define your own constructor using same signatures as base


public class PersonException : Exception
{
    public PersonException() : base() {} 
    public PersonException(string message) : base(message) {}
    public PersonException(string message, Exception innerException)
      : base(message, innerException) {}
}
Random

Random r = Random.Shared; //use this static class

Random Numbers

int dieRoll = r.Next(minValue: 1, maxValue: 7); //1-6 (max up to but not including)

double randDbl = r.NextDouble(); // 0.0 to less than 1.0

r.NextBytes(arrayOfBytes); //fills previously defined array with random bytes

Randomize Anything

string[] returnedArray = r.GetItems(choices: arrayOfAnything, length: 10); //pass in collection and how many you want back

r.Shuffle(arrayOfAnything); //returns array in shuffled order

Generating GUIDs

System.Guid is value type that combines timestamp, what version of UUID spec, and random # to create a unique ID.

Guid.Empty // 00000000-0000-0000-0000-000000000000

Guid g = Guid.NewGuid(); //returns random Guid

Guid g7 = Guid.CreateVersion7(DateTimeOffset.UtcNow); //version 7 sortable for dbs

Keywords

Reserved words
base — the inherited class reference
const — for hardcoded, never-changing fields, call with class name not obj instance
delegate — labels code as type delegate (intermediary function)
event — a special type of delegate that allows multiple assignment
init — used in place of set to make a property immutable after it’s been initialized
object => stand-in for System.Object
operator — used to define an operator method in a class
params — used to pass a variable number of parameters to a method as an array
partial — able to be merged with other so-named code
readonly — for fields, set at initialization then immutable
record — special type of immutable class
sealed — with class => prevent inheritance, with overridden method => prevent further overriding
static — for object members, call with class name not object instance ex: Math.Pi
this — current instance of an object (not used with static members)
value — implicitly passed into setters, what is being assigned
void — method doesn’t return a value
with — altering a record class after instantiation (makes a copy)