.NET
NET is a developer platform with tools and libraries for building any type of app, including web, mobile, desktop, games, IoT, cloud, and microservices.
Table of Contents
Intermediate Language (IL)
Code exists on many levels. Abstraction is a ladder. At the highest levels of abstraction, we have C# code. At a lower level, we have an intermediate language.
C# is compiled into that intermediate language.
Async/await
Notes from Brandon Minnick's Correcting Common Async/Await mistakes in .NET
ConfigureAwait(bool)
By default, all statements marked as await
have the ConfigureAwait(true)
flag. By setting the value to false
, we are explicitly saying that we do not wish to return back to the thread that called us.
Instead, we find any available thread and assign the following tasks to it. This is what the Context Switch does.
This is essential since we don't want to lock the main thread – which is responsible for dealing with user interactions and the UI.
async Task GetAllPosts() { List<Posts> posts = await GetPosts().ConfigureAwait(false); foreach (var post in posts) Console.WriteLine(post.Name); } // thread 1: initializes the post variable // thread 1: encounters await, spawns new thread // thread 2: assigns a new thread to be executed after the current one finishes // thread 2: finishes to GetPosts(), calls thread 3 // thread 3: continues with the instructions
Why use it?
if we don't need to return back to the calling thread, use it.
We should use it almost always. Unless we're dealing directly with the interface, we use it.
Wait()
Never, ever use it.
It locks the calling thread until the current thread is done, which is not ideal.
async Task<List<Posts>> GetAllPosts() { List<Posts> posts = await GetAllPosts().Wait(); if (posts == null || posts.Count() == 0) return new List<Posts>(); return posts; } // thread 1: initializes the posts variables // thread 1: encounters the await keyword // thread 1: spawns another thread: thread 2 // thread 2: locks the calling thread (thread 1) until GetAllPosts() is done // thread 2: finishes the GetAllPosts() operation, free thread 1 // thread 1: continues with the instructions
Other tips
- never use
async void
. When inside atry/catch
block, the main thread just continues to execute. - never use
.Wait()
or.Result()
- if synchronous, use
.GetAwaiter().GetResult()
- always use
async
EntityFramework
One-to-many relationship
Using the Generics lib:
using System.Collections.Generic;
We can define a navigation property to store multiple entities (list):
public virtual ICollection<Enrollment> Enrollments { get; set; }
It's defined as virtual
so they can use the EntityFramework functionality, like slow loading.
Destructor (Finalizers)
Finalizers (destructors in general) are used to perform necessary clean-ups when a class instance is being collected by the garbage collector.
class Car { ~Car { // finalizer // cleanup statements } }
A finalizer can also be defined as an expression body definition:
public class Destroyer { ~Destroyer() => Console.WriteLine($"The {GetType().Name} destructor is executing."); }
Dynamic Type
Dynamic binding defers binding - the process of resolving types, members, and operations from compile time to runtime.
Dynamic binding is useful when at compile time you know that a certain function, member, or operation exists, but the compiler does not.
dynamic d = GetsomeObject(); d.Quack();
We expect the runtime type of d to have a Quack method. We just can't prove it statically. Since d is dynamic, the compiler deferes binding Quack to d until runtime.
Extension Methods
Extension methods allows for an existing type to be extended with new methods without altering the definition of the original type.
public static class StringHelper { public static void IsCapitalized(this string s) { if (string.IsNullOrEmpty(s)) return false; return char.IsUpper(s[0]); } } Console.WriteLine("Perth".IsCapitalized());
Global Assembly Cache (GAC)
global
applies to the entire machine
assembly
what .NET calls its code-libraries (DLLs)
cache
a place to store things for common/faster access
Basically, it's a way to keep DLLs globally accessible without worrying about conflicts.
Everything is located at C:\\Windows\assembly
So the GAC must be a place to store code libraries so they're accessible to all applications running on the machine.
Interfaces
Interfaces are "signatures" of an object, they include the behavior from multiple sources in a class.
interface IEquatable<T> { bool Equals(T obj); }
public class Car : IEquatable<Car> { // implementation of IEquatable<T> interface public bool Equals(Car car) { return true; } }
Keywords
Structs
- Follows the same syntax as classes
- Within a
struct
declaration, fields cannot be initialized unless they are declared asconst
orstatic
- A
struct
cannot declare a parameterless constructor - Unlike classes,
structs
can be initialized without using anew
operator. - A
struct
cannot inherit from anotherstruct
orclass
. - A
struct
can implement interfaces - A
struct
cannot be null.
public struct TestStruct { // Fields, methods, properties and events ... }
Parameters
public class Keywords { public static void Main() { int val1 = 0; // must be assigned a value; int val2; // optional Keywords1(ref val1); Console.WriteLine(val1); Keywords2(out val2); Console.WriteLine(val2); } static void Keywords1(ref int value) => value = 7; static void Keywords2(out int value) => value = 9; // must be defined } /* Output 7 9 */
ref
Ref
keywords are used to pass an argument as a reference, meaning that when the value of that parameter
changes after being passed through the method, the new value is reflected in the new calling method.
out
Out
keywords is similar to ref
in that, they are used to pass an argument, but they differ in that
arguments passed using out
keyword can be passed without any value to be assigned to it.
Named and Optional Parameters
// optional arguments public void ExampleMethod(int required, int optionalStr = "default string") { } static void Main() { // named arguments PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug"); } static void PrintOrderDetails(string sellerName, int orderNum, string productName) {}
Reflection
Reflection is a C# language mechanism for accessing dynamic object properties in runtime. Typically, reflection is used to fetch the information about dynamic object type and object attribute values.
Get the members of a type
using System; using System.Reflection; using System.Reflection.Linq; public class Program { public static void Main() { var members = typeof(object) .GetMembers(Bindingflags.Public | BindingFlags.Static | BindingFlags.Instance); foreach (var member in members) { bool inherited = member.DeclaringType.Equals(typeof(object).Name); Console.WriteLine($"{member.Name} is a {member.MemberType}," + $"it has {(inherited ? "" : "not")} been inherited."); } } }
Get a method and invoke it
using System; public class Program { public static void Main() { var theString = "hello"; var method = theString .GetType() .GetMethod("Substring", new [] { typeof(int), typeof(int) }); // the types of the method var result = method.Invoke(theString, new object[] { 0, 4 }); Console.WriteLine(result); } }
Get Static Method and invoke it
var method = typeof(Math).getMethod("Exp"); var result = method.Invoke(null, new object[] { 2 }); // pass null as the first argument (no need for an instance) Console.WriteLine(result); // e^2
Stack and Heap
The stack
is responsible for keeping track on what's executing in our code.
The heap
is more or less responsible for keeping track of our objects (data).
Essentially, we have four main things we'll be putting in the stack and heap as our code is executing:
- Value types (
bool, byte, char, float, int
etc) - Reference Types (
class, interface, [[*Delegates][delegate]], object, string
) -> HEAP - Pointers (reference to a type)
- Instructions
Stack
Considering the following code:
public int AddFive(int pValue) { int result; result = pValue + 5; return result; }
AddFive()
andpValue | int
goes into the stack.- JIT compiles and executes the first instruction (
AddFive
). - As the method executes, we need some memory for the
result
variable and it is allocated in the stack. - The method finished the execution and the result is returned.
- All memory allocated on the stack is cleaned up by moving a pointer to the available memory address.
Heap
Value Types always go where they are declared, if a value type is declared outside a method, but inside a reference type it will be placed within the Reference Type of the heap.
public class MyInt { public int MyValue; } public MyInt AddFive(int pValue) { MyInt result new MyInt(); result.Myvalue = pValue + 5; return result; }
- Thread starts executing the method and its parameters are placed on the thread's stack.
- Because
MyInt
is a Reference Type, it is placed on the Heap and referenced by a Pointer on the Stack. - After
AddFive()
is finished executing, we clean it up. - We've left with an orphaned
MyInt
in the heap (there is no longer anyone in the stack pointing toMyInt
)
Delegates
Delegates are types that represent a reference to a method. They are used for passing arguments as reference to other methods.
class DelegateExample { public void Run() { // using class method InvokeDelegate(WriteToConsole); // using anonymous method DelegateInvoker di = delegate (string input) { Console.WriteLine(string.Format("di: {0}", input)); return true; } InvokeDelegate(di); // using lambda expression; InvokeDelegate(input => true); } public delegate bool DelegateInvoker(string input); public void InvokeDelegate(DelegateInvoker func) { var ret = func("Hello world"); Console.WriteLine("> Delegate returned {ret}"); } public bool WriteToConsole(string input) { Console.WriteLine($"WriteToConsole: {input}"); return true; } }
Threading
Creating and starting a second thread:
using System; using System.Threading; class Program { static void Main() { var thread = new Thread(Secondary); thread.Start(); } static void Secondary() => Console.WriteLine("Hello world"); }
Parallel.forEach loop
When the order of the output is not important, we can apply multiple threads to a foreach loop:
using System; using System.Threading; using System.Threading.Tasks; public class Program { public static void Main() { int[] Numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // single-threaded: Console.WriteLine("Normal foreach loop: "); foreach (var number in Numbers) Console.WriteLine(longCalculation(number)); // This is the Parallel (Multi-threaded solution): Console.WriteLine("Multiple threads: "); Parallel.forEach(Numbers, number => Console.WriteLine(longCalculation(number)); ); } private static int longCalculation(int number) { Thread.Sleep(1000); // sleep to simulate a long calculation return number * number; } }
StringBuilder
A String is immutable, meaning String cannot be changed once created.
StringBuilder sb = new StringBuilder(); // or StringBuilder sb = new StringBuilder("Hello world!!"); // allocate 50 bytes sequentially onto the memory heap. StringBuilder sb = new StringBuilder("Hello world", 50);
Important Methods of StringBuilder
Method Name | Description |
---|---|
StringBuilder.Append(valueToAppend) |
Appends the passed values to the end of the current String |
StringBuilder.AppendFormat() |
Replaces a format specifier passed in a string with formatted text |
StringBuilder.Insert(index, value) |
Inserts a string at the specified index of the current object |
StringBuilder.Remove(int start, int length) |
Removes the specified number of characters from the given position |
StringBuilder.Replace(old, new) |
Replaces characters with new characters |
Examples
Append
StringBuilder sb = new StringBuilder("hello", 50); sb.Append("World!"); sb.AppendLine("Hello C#"); sb.AppendLine("This is a new line."); Console.WriteLine(sb);
AppendFormat
StringBuilder sb = new StringBuilder("Hello world", 50); sb.Insert(5, " C#"); Console.WriteLine(sb);
Remove
StringBuilder sb = new StringBuilder("Hello world", 50); sb.Remove(6, 7); Console.WriteLine(sb);
Replace
StringBuilder sb = new StringBuilder("Hello world", 50); sb.Replace("World", "C#"); Console.WriteLine(sb);
Null Coalescing
The null-coalescing operator is used to set a default value for nullable types or reference types.
// to convert a non-nullable type to a nullable type: int? x = null;
string name = null; string myname = name ?? "Bruno";
Null conditional operator
Executes the definition on the right-hand side if the left-hand side operation is not null.
Returns 0
if people is null.
int length = people?.Length ?? 0;