This repo will serve as personal notes and quick reference guide for stuff that I pick up as I learn C#.
Goes nicely with c-sharp-katas repo, for some hands-on problem solving with C#.
Its not meant to be a C# learn the basics...
- The c# compiler compiles source code (a set of files with the
.csextension) into an assembly. - An assembly is the unit of packaging and deployment in
.NET. - An assembly can either be an application (executable) or a library.
- An executable has an entry point (
Mainor top-level statements), a library does not. - The purpose of a library is to be called upon (referenced) and used by other assemblies (applications or other libraries)
.NETitself is a set of libraries and a runtime environment (CLR, Common Language Runtime) at the same time.
- An executable has an entry point (
- A C# program can use top-level statements (a series of statements).
- In this case the program itself serves as the main entry point for the application. (C# generates a Main method automatically)
- If a
Mainmethod is present, C# will use this as the entry point.- A project that has top-level statements will ignore the
Mainmethod, with a warning.
- A project that has top-level statements will ignore the
- C# syntax is inspired by C and C++ syntax.
- Identifiers are names we can choose for our classes, methods, variables etc.
- System, variableName, Console, WriteLine...
- Whole word of Unicode characters.
- Can start with a letter or underscore.
- Case sensitive.
- By convention, identifiers for parameters or local variables should be written in camelCase while methods and classes in PascalCase.
- Keywords are reserved to the compiler, like
usingorint.- Cannot be used as identifiers.
- C# is a typed language, meaning we can either use predefined types or create custom ones.
- Example of predefined types are
int,stringetc... - To create a custom type we define a class, fields, constructor, and add a method to use it. We can then use that class as a type and instantiate it with
new. See example custom-type - A type contains data members and function members.
- The data members of UnitConverter is the field
ratio. - The function member of UnitConverter are the constructor and the method
convert.
- The data members of UnitConverter is the field
- Data is created when we instantiate a type.
- The
newoperator creates an instance of a type.
- Example of predefined types are
- Types can be converted to other types, as long as the types are compatible.
- A conversion always creates new value from existing value when dealing with value types, different for reference types.
- Conversions can either be implicit (done by the compiler) or explicit, we write it out or cast it ourselves.
- implicit conversion will only happen if the compiler can guarantee that no data will be lost during conversion
- Value types such as numeric, char, bool types etc, are always copied during assignment. Meaning that if we define int x = 7, and int y = x, we can change x but y will still be 7 (copy).
- Reference types such as strings, arrays etc are different, when assigned, we copy the reference to the object itself, meaning that if we mutate the original, the copy will also be affected.
- The var keyword can be used to instruct the compiler implicitly of a type
- For example,
var i = 3- i will implicitly become typeint.
- For example,
- If we already declared a type, we can write
newwithout having to repeat the type name. This is target-typed new expression.
- Strings are immutable reference type.
- String interpolation means we can include expressions in the string
$"Some text here and {expression}" - We can search with strings using indexes
str[2], for access only, we cant mutate a string.- indexOf and lastIndex of are useful, they take a
chartype.
- indexOf and lastIndex of are useful, they take a
- Every method of a string (because its immutable) returns a new string, leaving the original one untouched.
- Accessing
charis read-only.- StringBuilder creates a buffer of char values that can be converted to strings with the
ToString()method. - StringBuilder has some nice methods we can use
Append- Adds a char at the end of the buffer, it accepts among the following typeschar,string,int,double,bool,char[]...Insert- Inserts a char at a specific indexReplace- Replaces a charToString- Converts the entire buffer to a string.ToString(int startIndex, int length)to get a substringRemove(int startIndex, int length)— deletes charactersClear()- Clears everything from the buffer.
- StringBuilder creates a buffer of char values that can be converted to strings with the
- We can declare arrays with fixed sizes, or implicitly
char[] vowels = new char[] { 'a', 'e', 'i', 'o', 'u' }, shorthand version:char[] vowels = { 'a', 'e', 'i', 'o', 'u' } - From C#12, we can declare arrays with square brackets aswell
char[] vowels = ['a', 'e', 'i', 'o', 'u']; - This is called
collection expression, and can be used when calling methodssayTheVowels(['a', 'e', 'i', 'o', 'u']);- Collection expressions works with other collections too
List<string> planets = ["Mercury", "Venus", "Earth", "Mars"]
- Collection expressions works with other collections too
- We can refer to elements in an array using indices (relative to the end of an array).
vowels[^1]returns'u'vowels[^2]returns'o'- The
Indextype allows us to create a variable that is of type index, useful for using that variable when refering to elements in an array.Index lastItem = ^1and to use it we justConsole.WriteLine(myArr[lastItem]);
- We can use ranges to
sliceparts of an array.- Example array
int[] myArr = [1, 2, 4, 5]; int[] sliced = myArr[2..];- Slices the array from index 2 and to the end (exclusive)int[] sliced = myArr[1..2]- Gets 2int[] sliced = myArr[..2];- Gets 1 and 2- Like indices, we also got a
Rangetype Range firstTwoItems = 0..2;int[] sliced = myArr[firstTwoItems];- Gets us 1 and 2
- Example array
- We can combine both indices and ranges aswell
int[] sliced = myArr[^2..^1];- Gets 4 - starting from -2 upto and not inclusive -1
- To refresh my memory, parameters define the set of arguments that has to be provided when calling a method.
foo(8)- 8 is an argumentsstatic void foo(int p) {...}- p is a parameter
- In C#, we can control how parameters are passed with
ref,in,outandparams.ref- Passes an argument by reference, the default is that arguments are passed by value (a new variable copy is created).- The
refkeyword is both required when calling and declaring the method.int x = 10; // Here x is passed to f by value Foo(x); void Foo(int p) => p++; // p is 11, but x is still 10, because p is a copy // Here x is passed to foo by reference FooRef(ref x); void FooRef(ref int p) => p++; // x is now 11
out- Works just like ref, but it doesnt need to be assigned before going into the function. However it must be assigned before it comesoutthe function.int.TryParse("123", out int y);- We now have a variable y that is of int type and can be used.
in- Works just like ref, but its readonly, it prevents the reference to be mutated from within a method.// Here x is used, but it cannot be modified Bar(x); void Bar(in int p) { // p++; // Wont work, throws an error WriteLine(p); }
params- Allows any number of parameters to be passedFooBar(x, y); void FooBar(params int[] p) { foreach (int num in p) { WriteLine(num); // 10 // 123 } }
??- Null coalescing - says, if the operand to the left is not null, give it to me, otherwise use the right operand.??=- Null assignment - says, if the operand to the left is null, assign the operand to the right to the left.?.- Null conditional - says, if the property we are trying to access is null, return null and dont throw any errors
- classic switch
int cityNumber = 1; // Bergen string cityName = cityNumber switch { 0 => "Stavanger", 1 => "Bergen", _ => "Oslo" }; System.Console.WriteLine(cityName); // Bergen System.Console.WriteLine(giveMeACity(0)); // Stavanger string giveMeACity(int c) { switch (c) { case 0: return "Stavanger"; break; case 1: return "Bergen"; break; default: return "Oslo"; break; } } ;
- switch expressions
int cityNumber = 1; // Bergen string cityName = cityNumber switch { 0 => "Stavanger", 1 => "Bergen", _ => "Oslo" }; System.Console.WriteLine(cityName); // Bergen
- Namespaces are domains where type names must be unique.
- Types are organized into hierarchical name spaces.
- The
WriteLinemethod is a method of then namespaceSystem.Console. - We can create namespaces with the
namespacekeyword. - The dots in a namespace represents the hierarchy.
namespace Outer { namespace Inner { class Animal {} class Person {} } }
- To refer to a class, we must use its namespace
Outer.Inner.Animalto access its methods. - Types not defined in any namespace belong to the
global namespace. File-scopednamespaces is when everything in a file belongs a single namespace.namespace MyNameSpace; // Everything below belongs to this namespace class Animal {} // Belongs to MyNameSpace class Person {}
- File-scoped namespaces cannot be combined with top-level statements in the same file (compiler error).
- Imports a namespace and allows us to access everything from it once imported.
- With
using MyNameSpaceon top of a file we can use theAnimalclass without writing outMyNameSpace.Animalevery time.
Classes are commonly used for OOP as they provide Abstraction, Polymorphism, Inheritance and Encapsulation.
This is the heart of C#.
- Abstraction: Hides unnecessary implementation details from outside of the class.
- Polymorphism: Instances of a class can have the same methods, but with different behavior.
- Inheritance: Allows an instance of a class to inherit attributes and methods from an existing class.
- Encapsulation: Bundles methods and fields into a single unit (class). Allowing modifiers to define the access to each and one of those.
- A
classconsists of attributes and modifiers and class members.- attributes allow us to add metadata to classes, fields and methods.
- modifiers allow us to control the access of classes:
public,internal,static... - class members are such as
fields,methods,constructors...
- A
fieldis a variable.- a field can have a
readonlymodifier to prevent it from being modified after construction.
- a field can have a
- A
methodperforms an action in a series of statements.- a method can receive data from the caller by specifying
parametersand send back data to the caller using areturntype. - a method can specify a
voidreturn type, meaning it wont send anything back to the caller. - a method can also access its outside world by using
refandoutparameters.
- a method can receive data from the caller by specifying
- A
constructoris simply amethod, but named after the class name.- The constructor is used to run initialization code on new instances of a class.
- When designing the constructor we dont give it a return type, because its purpose is to run code on initiation.
Animal myPet = new Animal("Ella"); myPet.Bark(); // Woof public class Animal { public string name; // Field public Animal(string n) // The constructor, takes a string { name = n; // Assigns n to name. } public void Bark() { Console.WriteLine("Woof"); } }
- If we dont design a constructor on a class, the class will automatically implement a
parameterless-constructor. - if the baseclass has a parameterless constructor, the compiler inserts
: base()automatically for subclasses.- otherwise we must explicitly call
: base(..)
- otherwise we must explicitly call
- The
publicmodifier exposes class members to other classes.- by default:
- top-level types =
internal(Access is limited to the current project/assembly where it is defined) - members (fields, methods…) =
private
- top-level types =
- by default:
- The
staticallows us to use fields, methods only as part of a class itself and not an instance of it.- If a class is marked
static, it cannot be instantiated, meaning we cant create instances of it, all the members of this class must also be markedstatic. - See example:
- If a class is marked
- The
virtualmodifier allows subclasses of a class to modify a method. This enablespolymorphism.- When overriding on a subclass, we must use the
overridemodifier on it. - See example
- When overriding on a subclass, we must use the
- The
requiredmodifier can reduce the need for a class constructor, but this means every class instance will require this field to be assigned.- Constructors can still exist (and can be marked [SetsRequiredMembers] to satisfy the requirement)
- Introduced in C# 11. Useful with object initializers:
new Person { Name = "Alice" };where Name is required.
...
...
...
...