Digital Garden
Computer Science
C#
The Essentials

The Essentials

Commenting

// This is a normal comment
/// This is a documentation comment
/* This is a multiline comment
It can span over multiple lines which makes it a multiline comment */

Naming conventions

In C# almost everything is PascalCase. However, use camelCasing when naming private or internal fields, and prefix them with _. You also use camelCasing for method parameters.

Access Modifiers

public The code is accessible by any other code in the same assembly or another assembly that references it. privateThe code is only accessible within the same class or struct. protectedThe code is accessible within the same class, or in a class that inherits the class containing the protected internalThe code is only accessible within its own assembly, not from any other assembly. protected internal The code can be accessed by any code in the assembly in which it's declared, or from within a derived class in another assembly. readonly This prevents a field from being modified after construction. A read-only field can be assigned only in its declaration or within the enclosing type’s constructor. const A constant is evaluated statically at compile time and the compiler literally substitutes its value whenever used. A constant can be any of the built-in numeric types, bool, char, string, or an enum type.

Aliasing

Namespaces can also be aliased to allow shorter writing

using Dict = System.Collections.Generics.Dictionary<int, int>;

Types

Each type is defined as either a value type or a reference type. Just as in Java object ist the mother of all types meaning there is type unification.

Value and reference types

A variable of a value type contains an instance of the type. This differs from a variable of a reference type, which contains a reference to an instance of the type. By default, on assignment, passing an argument to a method, and returning a method result, variable values are copied. In the case of value-type variables, the corresponding type instances are copied. Reference types comprise all class, array, delegate, and interface types. This includes string. Multiple references can point to the same object. null means the reference points to no object.

Stack and the heap are the places where variables reside.

The stack is a block of memory for storing local variables and parameters. The stack grows and shrinks as a method or function is entered and exited.

The heap is the memory in which objects (i.e., reference-type instances) reside. The runtime has a garbage collector that periodically deallocates objects from the heap. An unreferenced object is eventually collected by the garbage collector.

Built-in types

C# provides a standard set of built-in types. These represent integers, floating point values, Boolean expressions, text characters, decimal values, and other types of data. There are also built-in string and object types. Most built-in types (all numeric types, char and bool) as well as custom struct and enum types are value types.

Custom types

You use the struct, class, interface, enum, and record constructs to create your own custom types.

Hierarchy of types in C#.

Boxing

Converting a value type into a reference type wraps up the value of intOne from the stack in to a heap object.

int intOne = 3;
object obj= intOne;

Unboxing

converting a reference type into a value type. Unwraps the value again.

int intOne = (int) obj;

Classes

class Hello
{
 private string name; // private field so camelCase
 private void Greet() {...}
 public static void Main(string[] args) {...}
 static Hello() { /* static constructor */}
}

Static fields

Static fields are Initialized before the static constructor is called and are called on the Class not the object.

Static constructor

Executed once per type before any instances of the type are created and any other static members are accessed.

Properties

Properties look like fields from the outside, but internally they contain logic, like methods do. If only get is defined then it is a read-only property.

public class Stock
{
 decimal currentPrice; // The private "backing" field
 public decimal CurrentPrice // The public property
 {
 get { return currentPrice; } // property accessors
 set { currentPrice = value; }
 }
}
 
Stock msft = new Stock();
msft.CurrentPrice = 30;
msft.CurrentPrice -= 3;
Console.WriteLine (msft.CurrentPrice);

Automatic properties

Compiler generates a private field internally and automatically does getter and setter like in java.

class Data
{
 public string CreateDate{ get; set; } = DateTime.Now; // initial value
}

Abstract classes

You can not create objects of abstract classes. Abstract methods have no implementation and are implicitly virtual meaning the need to be overwritten by the subclass.

abstract class Stream
{
 public abstract voidWrite(char ch);
 public void WriteString(strings)
 {
  foreach(char ch in s)
   Write(s);
 }
}
 
class File: Stream
{
 public override voidWrite(charch)
 {
  ...

String interpolation

int x = 4;
Console.WriteLine($"{s.Name} is {s.Width:F2}m wide and is {(s.IsRed ? "red":"not red")}");

Verbatim string literals

A verbatim string literal is prefixed with @ and does not support escape sequences and can also span multiple lines.

Console.WriteLine("\\\\server\\fileshare\\helloworld.cs");
Console.WriteLine(@"\\server\fileshare\helloworld.cs");
string escaped = "First Line\r\nSecond Line";
string verbatim = @"First Line
Second Line";
// True if your text editor uses CR-LF line separators:
Console.WriteLine(escaped == verbatim);
Console.WriteLine(@$"c:\{pathname}"); // Can combine with interpolation

Parameters

By default, arguments in C# are passed by value, this means that a copy of the value is created when passed to the method.

class Test
{
 static void Foo (int p)
 {
  p = p + 1; // Increment p by 1
  Console.WriteLine (p); // Write p to screen
 }
 static void Main()
 {
  int x = 8;
  Foo (x); // Make a copy of x
  Console.WriteLine (x); // x will still be 8
 }
}

Passing a reference-type argument by value copies the reference, but not the object.

class Test
{
 static void Foo (StringBuilder fooSB)
 {
  fooSB.Append ("test");
  fooSB = null; // copy of reference, doesn’t make sb null.
 }
 static void Main()
 {
  StringBuilder sb = new StringBuilder();
  Foo (sb);
  Console.WriteLine (sb.ToString()); // test
 }
}

ref modifier

To pass by reference. ref modifier is required both when writing and when calling the method.

class Test
{
 static void Foo (ref int p)
 {
  p = p + 1; // Increment p by 1
  Console.WriteLine (p); // Write p to screen
 }
 static void Main()
 {
  int x = 8;
  Foo (ref x); // Ask Foo to deal directly with x
  Console.WriteLine (x); // x is now 9
 }
}

out modifier

An out argument is like a ref argument except for the following:

  1. It need not be assigned before going into the function.
  2. It must be assigned before it comes out of the function.

Most commonly used to get multiple return values. out _ tells the compiler a so called discard.

class Test
{
 static void Split (string name, out string firstNames, out string lastName)
 {
  int i = name.LastIndexOf (' ');
  firstNames = name.Substring (0, i);
  lastName = name.Substring (i + 1);
 }
 static void Main()
 {
  string a, b;
  Split ("Stevie Ray Vaughan", out a, out b);
  // Or Split ("Stevie Ray Vaughan", out string a, out string b);
  // Or Split ("Stevie Ray Vaughan", out string a, out _);
  Console.WriteLine (a); // Stevie Ray
  Console.WriteLine (b); // Vaughan
 }
}

in modifier

An in parameter is similar to a ref parameter except that value cannot be modified by the method (doing so generates a compile-time error). This is most useful when passing a large value type(some big struct) to the method because it allows the compiler to avoid the overhead of copying the argument prior to passing it in while still protecting the original value from modification.

params modifier

Specify the params parameter modifier on the last parameter so that the method accepts any number of arguments of a particular type.

class Test
 {
 static int Sum (params int[] ints)
 {
  int sum = 0;
  for (int i = 0; i < ints.Length; i++)
  sum += ints[i]; // Increase sum by ints[i]
  return sum;
 }
 static void Main()
 {
  int total = Sum (1, 2, 3, 4);
  Console.WriteLine (total); // 10
 }
}

Optional parameters

void Foo (int x = 23) { Console.WriteLine (x); }

Named arguments

Go very well with optional parameters.

void Foo (int x, int y) { Console.WriteLine (x + ", " + y); }
void Test()
{
 Foo (x:1, y:2); // 1, 2
}

Ref locals and returns

A local variable that references an element in an array or field in an object.

int[] numbers = { 0, 1, 2, 3, 4 };
ref int numRef = ref numbers [2];
numRef *= 10;
Console.WriteLine (numRef); // 20
Console.WriteLine (numbers [2]); // 20

You can also return a ref local, this is called a ref return. If you omit the ref modifier on the calling side, it reverts to returning an ordinary value.

static string x = "Old Value";
static ref string GetX() => ref x; // This method returns a ref
static void Main()
{
 ref string xRef = ref GetX(); // Assign result to a ref local
 xRef = "New Value";
 Console.WriteLine (x); // New Value
}

Expression-bodied methods

A method that comprises a single expression, can be written as an expression-bodied method.

int Foo (int x) { return x * 2; }
int Foo (int x) => x * 2;
void Foo (int x) => Console.WriteLine (x); // Can also have void

Interfaces

Interface = only signatures, no implementation (apart from default implementations). Interfaces may contain methods, properties, indexers, events (no fields, constants, constructors, destructors, operators or nested types). Interface members are implicitly public abstract (virtual) and can extend other interfaces and be static. Classes and structs may implement multiple interfaces

public interface IList: ICollection, IEnumerable
{
 int Add(objectvalue); //methods
 bool Contains(objectvalue);
 bool IsReadOnly{ get; } //property
 object this[intindex] { get; set; } //indexer
 void Log(stringt) {Console.WriteLine(Prefix + t);} //default impl.
 static string Prefix=""; //static field
}

Jump statements

break

Same as Java. The break statement ends the execution of the body of an iteration or switch statement.

continue

The continue statement forgoes the remaining statements in a loop and makes an early start on the next iteration.

goto

The goto statement transfers execution to another label within a statement block.

int i = 1;
startLoop:
if (i <= 5)
{
 Console.Write (i + " ");
 i++;
 goto startLoop;
}

Controls

if, else if, else

Same as Java.

while and do while

Same as Java.

for

Same as Java.

foreach

The foreach statement executes a statement or a block of statements for each element in an instance of the type that implements the System.Collections.IEnumerable or System.Collections.Generic.IEnumerable<T> interface.

foreach (char c in "beer")
 Console.WriteLine(c);

switch

When more than one value should execute the same code, you can list the common cases sequentially. Unlike in Java there is no fall-through unless the case is empty. If you need fall-through you can use goto case/default.

switch (cardNumber)
{
case 13:
case 12:
case 11:
 Console.WriteLine ("Face card");
 break;
default:
 Console.WriteLine ("Plain card");
 break;
}

Pattern matching

Switching on a type is a special case of switching on a pattern. Each case clause specifies a type upon which to match, and a variable upon which to assign the typed value if the match succeeds (the “pattern” variable). The compiler lets us consume the pattern variables only in the when clauses.

object o = "ecnf"
switch(o)
{
 case byte b:
  Console .WriteLine($"I’m a byte with value {b});
  break;
 case string s when s == "ecnf"
  Console .WriteLine("I’m THE ecnf string");
  break;
 case string s:
  Console .WriteLine("I’m a string that contains {0}", s);
  break;
 default:
  Console.WriteLine("Don’t know anything");
  break;
}

Switch expressions

Switches can also switch on multiple values.

string module = "ecnf";
char grade = ‘B’;
string msg= (module, grade) switch
{
 ("ecnf",A’) => "Congrats, regards Yves Senn",
 ("ecnf",B’) => "Very good, regards Yves Senn",
 ("ecnf",C’) => "Good job, regards Yves Senn",
 ("oop1",A’) => "Nice one, regards Dieter Holz",
 ("oop1",B’) => "Well done, regards Dieter Holz",
 ("oop1",C’) => "Good job, regards Dieter Holz",
 _ => throw new InvalidArgumentException() // equivalent to default
}

Type checks and conversions

Implicit conversions are allowed when both of the following are true:

  1. The compiler can guarantee that they will always succeed.
  2. No information is lost in conversion.

Otherwise you need to use an explicit conversion

int x = 12345; // int is a 32-bit integer
long y = x; // Implicit conversion to 64-bit integer
short z = (short)x; // Explicit conversion to 16-bit integer

is operator

Checks whether an object is compatible with a given type and returns a Boolean. If the object reference is null it returns false.

Object o = new Object();
Boolean b1 = (o is Object); // b1 is true.
Boolean b2 = (o is Employee); // b2 is false.

The is operator is typically used as follows. However this causes 2 checks which can have an effect on performance.

if (o is Employee) {
 Employee e = (Employee) o;
}

It can also be used like this to make life easy:

if(b is D a)
{
     a.Foo("blBLA");
}

as operator

if o is compatible with the type returns a Employee as non-null reference to the same object. If o is not compatible with the type, returns null. Warning as operator will never throw an exception!!!!

Object o = new Object(); // Creates a new Object object
Employee e = o as Employee; // Casts o to an Employee
// The cast above fails: no exception is thrown, but e is set to null.

Conditional operator (ternary operator)

Has the form q ? a : b; thus, if condition q is true, a is evaluated, else b is evaluated:

static int Max (int a, int b)
{
 return (a > b) ? a : b;
}

Null operators

Null-Coalescing Operator

The ?? operator is the null-coalescing operator. It says, “If the operand to the left is non-null, give it to me; otherwise, give me another value.”

string s1 = null;
string s2 = s1 ?? "nothing"; // s2 evaluates to "nothing"

Null-Coalescing Assignment Operator

The ??= operator is the null-coalescing assignment operator. It says, “If the operand to the left is null, assign the right operand to the left operand.”

string s1 = null;
s1 ??= "something";
Console.WriteLine (s1); // something
s1 ??= "everything";
Console.WriteLine (s1); // something
// instead of
if (myVariable == null) myVariable = someDefault;

Null-Conditional Operator

The ?. operator is the null-conditional or “Elvis” operator. if the operand on the left is null, the expression evaluates to null instead of throwing a NullReferenceException.

System.Text.StringBuilder sb = null;
string s = sb?.ToString(); // No error; s instead evaluates to null
// same as
string s = (sb == null ? null : sb.ToString());

You can also use the null-conditional operator to call a void method:

someObject?.SomeVoidMethod();

If someObject is null, this becomes a “no-operation” rather than throwing a Null ReferenceException.

Nullable value types

int? length = sb?.ToString().Length; // OK: int? can be null