slama.dev

The C# programming language

19. 2. 2021; lecture notes

Preface

This website contains my lecture notes from a lecture by Pavel Ježek from the academic year 2020/2021. If you find something incorrect/unclear, or would like to contribute either text or an image, feel free to submit a pull request (or let me know via email).

Strings

Action Code
split s.Split(char);
split on multiple delimiters s.Split(delimiters);, delimiters is char[]
convert to integer int.Parse(string);, can throw!
length s.Length;

Interning

System.Text.StringBuilder

Interpolation

Console.WriteLine("{0}: Hello, {1}!", s1, s2);

// careful, what if s2 == "{0}"
Console.WriteLine("{0}: Hello, " + s2 + "!", s1, s2);

Chars

File I/O

Action Code
reading System.IO.StreamReader f = new System.IO.StreamReader(path);
read line f.ReadLine();
read chars int chars_read = f.Read(buffer, 0, BUFFER_SIZE);
writing System.IO.StreamWriter f = new System.IO.StreamWriter(path);
write line f.WriteLine(line);
close f.Dispose();

Classes

Constructor

class A {
	int x = stuff;
	
	A () {}
}

// is equivalent to (for each constructor!)
// note that stuff can be arbitrary code

class A {
	A () {
		int x = stuff;
	}
}
class A : Z {
	int x = something;
	
	A () {stuff}
}

// is equivalent to (for each constructor!)
// note that stuff can be arbitrary code

class A : Z {
	A () {
		int x = something;
		
		// constructor of Z (without parameters)
		
		stuff
	}
}
class A : Z {
	A () : base(/*parameters for constructor of Z*/) {stuff}
}

// is equivalent to (for each constructor!)
// note that stuff can be arbitrary code

class A : Z {
	A () {
		int x = something;
		
		// constructor of Z (with the appropriate parameters)
		
		stuff
	}
}

Class constructor

class A {
	static A() { }
}

Inheritance

class A { }      // some stuff
class B : A { }  // some stuff + some more stuff

A a = new A(); // is fine
a = new B();   // is also fine
Virtual/abstract/new methods
class A {
	public virtual void f() { Console.WriteLine("A"); }
	public virtual void g() { Console.WriteLine("A"); }
}

class B : A {
	public override void f() { Console.WriteLine("B"); }
	
	// new is optinal, suppresses a warning
	public new void g() { Console.WriteLine("B"); }
}

(new B()).f();  // prints B
(new B()).g();  // prints B

((A)(new B())).f();  // prints B
((A)(new B())).g();  // prints A
class Animal {
	public virtual void Name() { Console.WriteLine("Animal"); }
}

class Mammal: Animal {
	public override void Name() { Console.WriteLine("Mammal"); }
}

class Dog: Mammal {
	// new is optinal, suppresses a warning
	public new virtual void Name() { Console.WriteLine("Dog"); }
}

class Beagle: Dog {
	public override void Name() { Console.WriteLine("Beagle"); }
}

Dog pet = new Beagle();
pet.Name();              // prints Beagle

Animal pet = new Beagle();
pet.Name();              // prints Mammal
  A M D B
A.Name A M M M
D.Name     D B
Superclass to subclass conversion
is
as
B b = a as B;  // assigns `a` to `b` if it's of the valid type
               // this is the reason why `null is A` returns false

if (b != null) {
	// do stuff with `B b`
}

// is almost equivalent to (since C# 7.0)

if (a is B b) {
	// do stuff with `B b`
}

// `b` is not initialized here!
System.Object
System.ValueType
sealed

Variable scope

Local variables

if <something> {int b;} else {int b;}  // this is ok
int b;  // this is not (already declared in a nested block)
int e = 1, f;
if (e == 1) {f = 2;}
e = f; // error, f is not initialized in all paths

Exceptions

System.Exception

Property/Function Meaning
e.Message string error message
e.StackTrace string trace of method call stack
e.Source string app/object that threw it
e.ToString() string the formatted exception

Syntax

try {
	// stuff
} catch (Exception e) {
	// stuff only executed if the type matches the exception raised
} finally {
	// stuff always executed, even if the exception is not handled
	// for example, for closing IO/network resources
}

Common exception classes


using

Type x;
try {
	x = new Type();  // could raise an exception!
	
	// some code
} finally {
	if (x != null) x.Dispose();
}
using (type x = new Type()) {
	// some code
}

// is also equivalent to (since C# 8.0)
// the `Dispose` is called when the variable goes out of scope

using type x = new Type();

Properties

int Property {
	get { /* stuff to do */ }
	set { /* stuff to do (with a variable `value`) */ }
}

Auto-implemented

Instantiating objects with properties

A a = new A { x = 3; y = 6 };

// is literally the same as

A a = new A();
a.x = 3;
a.y = 6;

Parametric properties (indexers)

class File {
	FileStream s;
	
	public int this [int index] {
		get {
			s.Seek(index, SeekOrigin.Begin);
			return s.ReadByte();
		}
		
		set {
			s.Seek(index, SeekOrigin.Begin);
			return s.WriteByte((byte) value);
		}
	}
	
}

CIL Type System

Value types

Simple types

Implicit Conversions.

Nullable types

Reference types

Arrays
Action Code
create int[] array = new int[size];
create statically int[] array = {1, 2, 3};
fill with stuff Array.Fill(table, -1, 0, 256);, requires System
sort Array.Sort(array);, highly optimized
length .Length

Pointers

(un)boxing

Structures

using System;

struct A {
    public int num;
    public int num2;
}

struct B {
    public static A a1;
    public A a2;
}

static class Program {
    static void Main(string[] args) {
        B b = new B();
        Console.WriteLine(B.a1.num);  // is ok
        Console.WriteLine(b.a2.num);  // is also ok

        A a;
        // Console.WriteLine(a.num);  // is not ok

        a.num = 5;
        Console.WriteLine(a.num);  // is ok

        // A a2 = a;  // is not ok, the entire struct has not been initialized

        a.num2 = 5;

        A a2 = a;  // is now ok
    }
}

readonly

  • used to set fields not assignable, except in the constructor
  • can also be used in autoimplemented properties (int X { get; } = 5;)

Visibility

Access Modifiers Access is…
public not restricted.
private limited to the containing type.
protected limited to the containing class derived types.
internal limited to the current assembly.
protected internal limited to the current assembly OR same/derived types.
private protected limited to the current assembly AND same/derived types.
  • by default:
    • visibility of classes/interfaces/structs is internal
    • visibility in classes is private
    • visibility in interfaces is public
      • it doesn’t make much sense to be private, since it’s a public contract
class A {
	private int x;
	
	// is ok, x is private in A
	public int GetX() {
		return x;
	}
	
	// is ALSO OK, since the code is inside A (B : A)
	public static void SetXOnB(B b) {
		b.x = 30;
	}
}

=>

  • syntactic sugar for:
    • when a non-void method returns something
    • when a void method has only one command

??, ??=, ?.

  • conditional code execution, depending on whether something is null or not
  • a ?? b returns a, if it isn’t null, else b
  • x ??= y will be assigned if y isn’t null
  • x?.m() will call the method if x isn’t null
  • x?.f will get the field value, if x isn’t null

ref

  • a tracking reference
  • address is passed and automatically de-referenced
  • can point to heap/stack/static data
  • parameter must be a variable, not an expression (like a number)
  • makes sense if we really want to modify the passed variable
void Inc(ref int x) { x += 1; }

void f() {
	int val = 3;
	Inc(ref val);  // val will be 4
}
  • calling a method on an object is just a shortcut for doing Class.function(ref this, )

out

  • an alternative to ref
  • useful for returning/setting multiple things in a function
  • the CIL code is exactly the same, but the compiler checks, whether each out parameter has been assigned to (since the caller wouldn’t know about the state of the variable)
    • we can’t not assign the parameters (we have to at least assign dummy values)
void Read(out int first, out int next) {
	first = Console.Read();
	next = Console.Read();
}

void f() {
	int first, next;
	Read(out first, out next);
}
  • the variable can be declared as a part of the function call
int a;
if (tryParse(something, out a)) { Console.WriteLine("We failed: " + a); }
Console.WriteLine("We didn't fail: " + a);

// is the same as

if (tryParse(something, out int a)) { Console.WriteLine("We failed: " + a); }
Console.WriteLine("We didn't fail: " + a);

var

  • derived at compile time, depending on what is on the right side
  • if we can’t determine the type, the code won’t compile
    • var x;
    • var y = (1, 2, 3);
    • var z = null
  • the declaration is a comment – it’s unwise to write var everywhere:
    • var name = GetName(); – what does it return?
    • var d = new List(); – what if I want to change List to a HashSet later?

Interfaces

  • a contract – we can assign any class that implements an interface to variables with an interface (when we only require the functionality of the given interface)
    • we can do the same for a struct, but it involves boxing
  • can’t be instantiated, since it has no code
  • not entirely an abstract class
    • classes/interface can implement multiple interfaces
    • classes can inherit only a single class
    • interfaces can’t have code
  • note that each interface „inherits“ System.Object, since all objects inherit it too
    • not literally, it’s #justcompilerthings

Heaps and GC

  • two heaps; behavior of the garbage collector to the two heaps is different
  • real limit is around 1.4 GB1.4\ GB (for 32-bit systems)
    • OutOfMemoryException could happen sooner, since we could have a lot of holes in the given heap and the new object wouldn’t fit in – heap fragmentation
    • happens easily when resizing (dynamic array, for example), since we’re creating a new array of twice the size, that has to co-exist with the old one for a bit
  • segment – reservation (virtual memory); around  16 MB~16\ MB
    • varies greatly on the architecture and GC configuration!
  • kvantum – commit (physical memory); around  8 kB~8\ kB
    • varies greatly on the architecture and GC configuration!
  • GC is generational
    • gen 0 – allocated here
    • gen 1 – survives a GC
    • gen 2 – survives another GC
    • GC of a given generation checks the generation and all above
    • the next generations are checked only if the previous ones didn’t free too much memory
    • ephemeral segment – the current newest segment of the garbage collector
      • this is where GC happens
      • once it is full, a new one is added and the old one becomes generation 2 segment
  • GC class – for interacting with the GC:
    • checking the generation of the object
    • checking the number of generation collections
    • forcing a collection of a given generation
    • should be the last resort, since it usually does things well
  • GC     \implies no memory leaks is not true (or, well, to an extent…):
    • a global cache used in some classes doesn’t get collected, since there is a reference to it

Large Object Heap (LOH)

  • if it’s larger than 85 000 B85\ 000\ B
  • usually reached when the object contains an array
  • no heap compacting (don’t move objects when others die)

Small Object Heap (SOH)

  • if it’s smaller than 85 000 B85\ 000\ B

CIL

  • common intermediate language
  • formerly known Microsoft Intermediate Language (MSIL) or Intermediate Language (IL)
  • intermediate language binary instruction set for the CLI specification
  • executed by CLI-compatible runtime like CLR
  • object-oriented, stack-based, typically JITted into native code

GAC

  • global assembly cache – stores assemblies to be shared by computer applications

JIT

  • just in time – when it’s needed
  • the default unit is 1 method
  • function calls are translated to calls to stubs
    • short calls that call JIT and fix references to the method in the code
  • compilation is spaced into the running time of the program
    • can mess benchmarks up – make sure we’re testing when the method has already been JITted
  • also takes care of optimizations, since optimizing CIL code doesn’t make much sense
    • means that they can’t be overly aggressive
AOT
  • ahead-of-time – before it’s needed
  • ngen.exe – pre-JITting code
  • more intensive optimizations
  • JITting can still happen when it’s needed, though
  • will be an important feature in .NET 5

CLR

  • run-time environment for the .NET framework
  • responsible for running .NET programs, regardless of the language
  • contains things like GC and JIT compiler

BCL

  • base class library
  • types for built-in CLI data types, basic file access, collections, formatting,…

.NET

  • BCL + CLR

Tiered compilation

  • fast-running JIT generating bad code / slow-running JIT generating good code
  • bad code is JITted for the first run of a method
  • after a number of calls, better code is JITted
    • can run in a separate thread and replace the bad code later
    • makes benchmarking more difficult

Self-contained .exe

  • executable containing the program and all needed assemblies
  • produced using dotnet publish

Method inlining

  • JIT compiler optimization that moves the body of the method to where it is called
  • can’t always be done (recursive functions)
  • generally happens for smaller (<32 B<32\ B of machine code) methods that aren’t too complicated (no try/catch, for example)
  • can be forced/disabled using [MethodImpl]

Demand loading

  • assemblies are used and loaded into memory only when it’s necessary
  • it it’s missing, then the program won’t crash, unless it’s explicitly used

(De)compiling

  • ILSpy – open-source .NET assembly browser and decompiler
  • ilasm – generates an executable from a text representation of CIL code

Data structures

Dictionaries

  • using System.Collections.Generic;
Action Code
create Dictionary d = new Dictionary();
contains d.ContainsKey(element);
add d[index] = element;

Queues

Action Code
create Queue q = new Queue();
add q.Enqueue(element);
pop q.Dequeue(element);
size q.Count;
peek q.Peek();

Miscellaneous

NuGet

  • package manager

BenchmarkDotNet

  • benchmarks

prg.exe.config (XML)

  • configures .NET (garbage collection settings,…)
    • server mode doesn’t do garbage collection as frequently, to be faster
    • client mode collects a lot (could be up to 30%)

CLS

  • common language specification
  • what a language must do in order to be .NET-compliant
  • specifies things like minimum types:
    • sbyte, ushort are not compliant, so making them parameters of a function called from some other DLL might not be the best idea; on the other hand, implementation details are quite fine

Arithmetic overflows

checked {
	// kód
}
  • is not controlled when functions are called from this block (how could it, when they’re probably already translated…)

typeof(Class)

  • return the Type instance of the class
  • useful when we’re comparing types of variables

nameof(x) [Microsoft Docs]

  • useful when debugging, when showing exceptions to users…
  • better to do than "x", since we can rename using any IDE easily
Console.WriteLine(nameof(System.Collections.Generic));  // output: Generic
Console.WriteLine(nameof(List<int>));                   // output: List
Console.WriteLine(nameof(List<int>.Count));             // output: Count
Console.WriteLine(nameof(List<int>.Add));               // output: Add

var numbers = new List<int> { 1, 2, 3 };
Console.WriteLine(nameof(numbers));        // output: numbers
Console.WriteLine(nameof(numbers.Count));  // output: Count
Console.WriteLine(nameof(numbers.Add));    // output: Add

NUnit tests

using System;
using NUnit.Framework;

public class Tests
{
	[SetUp]
	public void Setup() { /* do something for setup (optional) */ }
	
	[Test]
	public void NameOfTheTest()
	{
		Assert.AreEqual("a", "a");
		Assert.AreNotEqual("a", "b");
		Assert.IsTrue(true);
		Assert.IsFalse(false);
		
		Assert.Throws<ArgumentException>(() =>
		{
			This doesn't throw!
		});
	}
}