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)
objectsPerson 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 nullint? authorNameLength = authorName?.Length;
//assign a specific value if nullint 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);
Symbol | Meaning | Symbol | Meaning |
---|---|---|---|
^ | 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) |
\d | A single digit | \D | A single non-digit |
\s | A space | \S | A non-space |
\w | A word | \W | A 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
Type | Byte(s) | Min | Max |
---|---|---|---|
sbyte | 1 | -128 | 127 |
byte | 1 | 0 | 255 |
short | 2 | -32768 | 32767 |
ushort | 2 | 0 | 65535 |
int | 4 | -2147483648 | 2147483647 |
uint | 4 | 0 | 4294967295 |
long | 8 | -9223372036854775808 | 9223372036854775807 |
ulong | 8 | 0 | 18446744073709551615 |
Int128 | 16 | -170141183460469231731687303715884105728 | 170141183460469231731687303715884105727 |
UInt128 | 16 | 0 | 340282366920938463463374607431768211455 |
Half | 2 | -65500 | 65500 |
float | 4 | -3.4028235E+38 | 3.4028235E+38 |
double | 8 | -1.7976931348623157E+308 | 1.7976931348623157E+308 |
decimal | 16 | -79228162514264337593543950335 | 79228162514264337593543950335 |
NOTE: sizeof(type) for capitalized types (Int128, UInt128, Half) only works in unsafe code blocks
In System.Numerics
Type | Note | ||
---|---|---|---|
BigInteger | really large integers | 256 MB (646.5 mill digits) | |
Complex | a + bi — i is always √(-1) | new(real: 4, imaginary: 2); | ‘imaginary’ means what to be multiplied by √(-1) |
Weird Number Stuff
Expression | Value |
---|---|
double.NaN | NaN |
double.PositiveInfinity | ∞ |
double.NegativeInfinity | -∞ |
int / 0 | DivideByZeroException |
0.0 / 0.0 | NaN |
3.0 / 0.0 | ∞ |
-3.0 / 0.0 | -∞ |
3.0 / 0.0 == double.PositiveInfinity | True |
-3.0 / 0.0 == double.NegativeInfinity | True |
0.0 / 3.0 | 0 |
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
Format | Returns (en-US) |
---|---|
:D | Monday, January 15, 2009 |
:d | 6/15/2009 |
:f | Monday, January 15, 2009 12:00 AM |
:F | Monday, January 15, 2009 12:00:34 AM |
:g | 6/15/2009 1:45 PM |
:G | 6/15/2009 1:45:30 PM |
:M or :m | January 15 |
:Y or :y | January 2009 |
:0 or :o | 2009-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 classvar 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 fieldsvar 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 topusing 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 shorthandCat 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:
Interface | Method(s) | Description |
---|---|---|
IComparable | CompareTo(other) | To order or sort instances of a type |
IComparer | Compare(first, second) | To order or sort instances of a primary type |
IDisposable | Dispose() | To release unmanaged resources |
IFormattable | ToString(format, culture) | To format in a culture-aware way the value of an object |
IFormatter | Serialize(stream, object) Deserialize(stream) | To convert an object to and from a byte stream for transfer or storage |
IFormatProvider | GetFormat(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 syntaxPerson child = bob[0];
//indexer gets Child object at position 0bob[0] = newbabyObject;
//indexer set – overwrites the 0 object in collectionbob["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
ThrowIfZeroArgument.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 eventpublic 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 eventharry.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 returnWriteLine("something");
— adds carriage return
Detecting Key Presses
Write("Press key: ");
— user promptConsoleKeyInfo 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)