Skip to content

7. Basics

C# is an object-oriented programming language with garbage collection and single inheritance. It supports many modern paradigms and syntactical sugar. In the following you will find some coding snippets how to program in C#, all of them run in a simple console application. Make sure, you learn C# and not C++ in the dress of C#, i.e. do not just learn the new syntax, but start to think the C# way. The code inspection gives you some hints, how to use the C# concepts. Check and understand the hints given by the code inspection.

You may find examples, showing functional programming with C#. However, there is F# which is better for functional programming.

7.1 StringBuilder and String-Templates

In C#, the string keyword is an alias for String; therefore, String and string are equivalent.

String objects are immutable: they can't be changed after they've been created. All of the String methods and C# operators that appear to modify a string actually return the results in a new string object. Hence, for better performance use StringBuilder if you need to concatenate many strings.

C# also allows verbatim string interpolation (strings that can span multiple lines and interpret escape sequences literally), for example across multiple lines, using the $@ or @$ syntax.

Below you will find some examples addressing strings, see also

// Initialize with a regular string literal.
string oldPath = "c:\\Program Files\\Microsoft Visual Studio 8.0";

// Initialize with a verbatim string literal.
string newPath = @"c:\Program Files\Microsoft Visual Studio 9.0";

string embeddedXML = """
       <element attr = "content">
           <body style="normal">
               Here is the main text
           </body>
           <footer>
               Excerpts from "An amazing story"
           </footer>
       </element >
       """;

var jh = (firstName: "Jupiter", lastName: "Hammon", born: 1711, published: 1761);
Console.WriteLine($"He'd be over {Math.Round((2018d - jh.born) / 100d) * 100d} years old today.");
Console.WriteLine($@"{jh.firstName} {jh.lastName}
    was an African American poet born in {jh.born}.");
Console.WriteLine(@$"He was first published in {jh.published}
at the age of {jh.published - jh.born}.");

var pw = (firstName: "Phillis", lastName: "Wheatley", born: 1753, published: 1773);
Console.WriteLine("{0} {1} was an African American poet born in {2}.", pw.firstName, pw.lastName, pw.born);

using System;
using System.Text;
int MyInt = 25;
StringBuilder myStringBuilder = new StringBuilder("Your total is ");
myStringBuilder.AppendFormat("{0:C} ", MyInt);

7.2 Generics

"Generic classes encapsulate operations that are not specific to a particular data type 1." In C++ generics are known as templates. However, there are some differences, see 2.

Example for Generics1

List<int> list = new List<int>();

static void Swap<T>(ref T lhs, ref T rhs)
{

    T temp;
    temp = lhs;
    lhs = rhs;
    rhs = temp;
}

7.3 Lambdas - Arrow Operator - =>

Lambdas are anonymous functions and are best thought as a mathematical map to, i.e.

(a,b,c) → f(a,b,c)
Code example for lambda3

int[] numbers = { 4, 7, 10 };

int product = numbers.Aggregate(1, (int interim, int next) => interim * next);

7.4 Delegates

Delegates 4 are basically function pointers. You may use delegates to pass a method as a parameter. It declares the parameters with type and the return type.

Example code for a delegate4

public delegate void Callback(string message); // declare

public static void DelegateMethod(string message) // method that fits the signature
{
    Console.WriteLine(message);
}
Callback handler = DelegateMethod; // instantiate
handler("Hello World"); // call and hence 'Hello World' will be on the console

A delegate can call more than one method when invoked. This is referred to as multicasting.

There are many predefined delegates

  • Action for methods without return values[@action-delegate]
  • Func for methods with return values[!@func-delegate]
    public delegate void Action<in T>(T obj);
    public delegate TResult Func<in T,out TResult>(T arg);
    // and many more signatures of the same kind
    

Tip

You hardly ever need to declare your own signature but should use these predefined types. These predefined delegates exist with many parameters.

7.5 Types

Although C# is a garbage collected programming language you need to distinguish and know of reference and value types. "There are two kinds of types in C#: reference types and value types. Variables of reference types store references to their data (objects), while variables of value types directly contain their data."5

In C#, reference types are closer to C++ pointers than to C++ references. C# references can be null and have assignment semantics similar to pointers, while C++ references are more similar to aliases or alternative names for the same object. In C#, reference type variables sit on the stack, but they hold the address of an object on the heap, much like pointers in C++.Therefore, there are significant differences between C# reference types and C++ reference types in terms of nullability and assignment semantics2.

7.5.1 Value Types

A value type can be one of the two following kinds:

7.5.2 Reference Types

The following keywords are used to declare reference types:

C# also provides the following built-in reference types:

7.5.3 Boxing

Boxing and Unboxing
Boxing and Unboxing

"Boxing is the process of converting a value type to the type object or to any interface type implemented by this value type. Boxing is used to store value types in the garbage-collected heap. Boxing is an implicit conversion of a value type to the type object or to any interface type implemented by this value type."6

7.5.4 Null Safety

The default value for all reference types is null. Value type have a default value, e.g. the default value for an int is 0. To define a nullable value type you add a question mark.

Examples using nullable value types:

int? first;            // first is implicitly null (uninitialized)
int? second = null;    // second is explicitly null

To ensure null safety you should use ? for reference types too.

Warning

In C# the ? is just a hint for the compiler and causes warnings only.

7.6 Object Orientation

C# supports single inheritance, multiple constructors, a short notation for getters/setters and default values.

To get familiar with the syntax, here are some snippets how to declare classes and interfaces and how to use them.

namespace oo;  

public interface IStudent  
{  
    string Name { get; }  
    int CalculateLearningTime(DateTime startTime);  
}  

public class LazyStudent : IStudent  
{  
    public string Name { get; set; } = "???";  

    public int CalculateLearningTime(DateTime startTime)  
    {        return 1;  
    }}  

public class ElectiveStudent : IStudent  
{  
    private readonly Random _random = new();  
    private readonly List<string> _toDos = new();  
    public ElectiveStudent(string name)  
    {        Name = name;  
        _toDos.Add("first: understand the exercise");  
        _toDos.Add("second: make a plan");  
        CurrentGrade = 3.0f;  
    }    public ElectiveStudent():this("Studious")  
    {            }  

    public float CurrentGrade { get; }  

    public string Name { get; set; }  

    public int CalculateLearningTime(DateTime startTime)  
    {        return _random.Next(7, startTime.Hour); // makes no sense  
    }  

    public void PrintToDos()  
    {        Console.WriteLine("my todos: ");  
        foreach (var toDo in _toDos)  
        {            Console.WriteLine(toDo);  
        }    }}  

internal static class Program  
{  
    private static void Main()  
    {        var robert = new ElectiveStudent("Robert");  
        var now = DateTime.Now;  
        AnalyzeStudent(robert, now);  
        robert.PrintToDos();  
        Console.WriteLine($"my current grade is {robert.CurrentGrade}");  
        Console.WriteLine("------------");  
        var other = new ElectiveStudent();  
        AnalyzeStudent(other, now);  
        Console.WriteLine("------------");  
        var anonymous = new LazyStudent  
        {  
            Name = "Anonymous"  
        };  
        AnalyzeStudent(anonymous, now);  
    }  
    private static void AnalyzeStudent(IStudent student, DateTime now)  
    {        Console.WriteLine(  
            $"My name is {student.Name} and I am available for {student.CalculateLearningTime(now)} hours to learn starting from {now}.");  
    }}

7.6.1 Records -- C# 9

"You use the record modifier to define a reference type that provides built-in functionality for encapsulating data. C# 10 allows the record class syntax as a synonym to clarify a reference type, and record struct to define a value type with similar functionality."7

Basically, you use it for simple data classes and hence a lightweight, immutable way to encapsulate data.

public record Person(string FirstName, string LastName);
public record Person
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
};

7.7 Events

With events an object may inform other objects, that something interesting happened, e.g. a button was clicked, a timeout reached, a list was changed, a new message arrived ... This is typically solved with the publish-subscriber-pattern -- also known as observer, event-subscriber, Listener.

Subscriber-Publisher
Publish-Subscribe pattern [^9]

The standard is to define events based on the following delegate

EventHandler Delegate

public delegate void EventHandler(object? sender, EventArgs e);

The following example shows it in a real world example, out of dofactory

namespace Observer.NetOptimized;

using static System.Console;

using System;

/// <summary>
/// Observer Design Pattern
/// </summary>
public class Program
{
    public static void Main()
    {
        // Create IBM stock and attach investors
        var ibm = new IBM(120.00);

        // Attach 'listeners', i.e. Investors
        ibm.Attach(new Investor { Name = "Sorros" });
        ibm.Attach(new Investor { Name = "Berkshire" });

        // Fluctuating prices will notify listening investors
        ibm.Price = 120.10;
        ibm.Price = 121.00;
        ibm.Price = 120.50;
        ibm.Price = 120.75;

        // Wait for user
        ReadKey();
    }
}

// Custom event arguments
public class ChangeEventArgs : EventArgs
{
    // Gets or sets symbol
    public string Symbol { get; set; }

    // Gets or sets price
    public double Price { get; set; }
}

/// <summary>
/// The 'Subject' abstract class
/// </summary>
public abstract class Stock(string symbol, double price)
{
    protected string symbol = symbol;
    protected double price = price;

    // Event
    public event EventHandler<ChangeEventArgs> Change = null!;

    // Invoke the Change event
    public virtual void OnChange(ChangeEventArgs e)
    {
        Change?.Invoke(this, e);
    }

    public void Attach(IInvestor investor)
    {
        Change += investor.Update;
    }

    public void Detach(IInvestor investor)
    {
        Change -= investor.Update;
    }

    // Gets or sets the price
    public double Price
    {
        get { return price; }
        set
        {
            if (price != value)
            {
                price = value;
                OnChange(new ChangeEventArgs { Symbol = symbol, Price = price });
                WriteLine("");
            }
        }
    }
}

/// <summary>
/// The 'ConcreteSubject' class
/// </summary>
public class IBM : Stock
{
    // Constructor - symbol for IBM is always same
    public IBM(double price)
        : base("IBM", price)
    {
    }
}

/// <summary>
/// The 'Observer' interface
/// </summary>
public interface IInvestor
{
    void Update(object sender, ChangeEventArgs e);
}

/// <summary>
/// The 'ConcreteObserver' class
/// </summary>
public class Investor : IInvestor
{
    // Gets or sets the investor name
    public string Name { get; set; } = null!;

    // Gets or sets the stock
    public Stock Stock { get; set; } = null!;

    public void Update(object sender, ChangeEventArgs e)
    {
        WriteLine("Notified {0} of {1}'s " +
            "change to {2:C}", Name, e.Symbol, e.Price);
    }
}

7.8 Operators

Besides the typical arithmetic operators, there are some more

  • is, as, type of -- checks the type
  • ?: -- the ternary or conditional operator is a short form for if-else
  • ! (null-forgiving) -- use this operator for a nullable object, which should at this line of code not be null, it will suppress all nullable warnings and has no effect on runtime
  • ?? and ??= -- the null-coalescing operators, this operator shortens code with first null check and then assigning values or executing a statement Below you will find code snippets showing, how to use the above mentioned operators.
    string GetWeatherDisplay(double tempInCelsius) => tempInCelsius < 20.0 ? "Cold." : "Perfect!"; // if ..<20 then Cold else Perfect
    
    int b = a ?? -1; // if a is null b=-1, else b=a
    
    x ??= y; // if x is null assign y to x -- good to ensure non-null values
    

7.9 Extension Methods

"Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are static methods, but they're called as if they were instance methods on the extended type."9

They are a powerful way to add features to library classes and access them as if they were build in methods. See the following examples.

public static int WordCount(this string str)  
{  
    return str.Split(new char[] { ' ', '.', '?' },  
        StringSplitOptions.RemoveEmptyEntries).Length;  
}
string greeting = "Hello Extension Methods";
int numberWords = greeting.WordCount();

7.10 LINQ

With LINQ you can do almost anything you can do with SQL for databases on any IEnumerable, e.g. select, order, group values of a list of objects.

Warning

The execution of a linq query is always deferred to the time, it is needed/executed. This makes a difference if you use variables in your query. The query is executed when you access its result and hence the value of a variable might have changed.

Examples for Language Integrated Query (LINQ)

// Specify the data source.
int[] scores = [97, 92, 81, 60];

// Define the query expression.
IEnumerable<int> scoreQuery =
    from score in scores
    where score > 80
    select score;

// Execute the query.
foreach (var i in scoreQuery)
{
    Console.Write(i + " ");
}

// Output: 97 92 81

Video LINQ Query Expressions From, Where, Orderby, and Select

Watch and understand the video 📹 LINQ Query Expressions From, Where, Orderby, and Select, where they discuss some more features of LINQ, using VS Code.

7.11 Attributes and Reflection

Attributes are declarative information attached to a class or method or other structure that may be read/analyzed at runtime using reflection. This mechanism is widely used in C#, e.g. to mark a class as serializable (see next chapter) or to write less boilerplate code with MVVM.

In the following you find an example for the pre-defined attribute Obsolete.

[Obsolete("This class is obsolete; use class B instead")]
class A
{
    public void F() {}
}

7.12 Serialization

graph LR
 A[Object] --> B[String]
 A --> C[Other Object]
 A --serialize---> D[Linear Stream]
 D --> E[File/...]
 E --deserialize---> F[Object]
 F --> G[String]
 F --> H[Other Object]
Serialization is the process of transforming an object graph into a linear stream of bytes. These bytes may be saved to the file system and e.g. after restarting the app you may de-serialize and re-create the object.

The following example is from How to write .NET objects as JSON (serialize).

using System.Text.Json;

namespace SerializeToFile
{
    public class WeatherForecast
    {
        public DateTimeOffset Date { get; set; }
        public int TemperatureCelsius { get; set; }
        public string? Summary { get; set; }
    }

    public class Program
    {
        public static void Main()
        {
            var weatherForecast = new WeatherForecast
            {
                Date = DateTime.Parse("2019-08-01"),
                TemperatureCelsius = 25,
                Summary = "Hot"
            };

            string fileName = "WeatherForecast.json"; 
            string jsonString = JsonSerializer.Serialize(weatherForecast);
            File.WriteAllText(fileName, jsonString);

            Console.WriteLine(File.ReadAllText(fileName));
        }
    }
}
// output:
//{"Date":"2019-08-01T00:00:00-07:00","TemperatureCelsius":25,"Summary":"Hot"}

7.13 Exercises

You're developing a console application for the Contoso Petting Zoo that coordinates school visits. The Contoso Petting Zoo is home to 18 different species of animals. At the zoo, visiting students are assigned to groups, and each group has a set of animals assigned to it. After visiting their set of animals, the students will rotate groups until they've seen all the animals at the petting zoo.

By default, the students are divided into 6 groups. However, there are some classes that have a small or large number of students, so the number of groups must be adjusted accordingly. The animals should also be randomly assigned to each group, so as to keep the experience unique.

The design specification for the Contoso Petting Zoo application is as follows:

  • There are currently three visiting schools

    • School A has six visiting groups (the default number)
    • School B has three visiting groups
    • School C has two visiting groups
  • For each visiting school, perform the following tasks

    • Randomize the animals
    • Assign the animals to the correct number of groups
    • Print the school name
    • Print the animal groups

Compare your solution with Guided Project.

Event - Delegate

  • What is a delegate?
  • What is an event?
  • Is it possible to return a value with an event?
  • How are they different?
  • How do they play together? (Event-Delegate-Pattern)

Define a class BankAccount with the following features

  • It has a 10-digit number that uniquely identifies the bank account.
  • It has a string that stores the name or names of the owners.
  • The balance can be retrieved.
  • It accepts deposits.
  • It accepts withdrawals.
  • The initial balance must be positive.
  • Withdrawals can't result in a negative balance.
  • Offer several options to retrieve a specific list of transactions.

Compare your solution with Explore object oriented programming with classes and objects.


  1. Bill Wagner. Generic classes - c# programming guide - c#. 2022. URL: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/generic-classes (visited on 19.01.2024). 

  2. Bill Wagner. Differences between c++ templates and c# generics - c# programming guide - c#. 2023. URL: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/differences-between-cpp-templates-and-csharp-generics (visited on 19.01.2024). 

  3. Bill Wagner. Lambda expressions - lambda expressions and anonymous functions - c#. 2023. URL: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions (visited on 19.01.2024). 

  4. Bill Wagner. Delegates - c# programming guide - c#. 2022. URL: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/ (visited on 19.01.2024). 

  5. Bill Wagner. Reference types - c# reference - c#. 2023. URL: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/reference-types (visited on 05.03.2024). 

  6. Bill Wagner. Boxing and unboxing. 2021. URL: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing (visited on 16.04.2024). 

  7. Bill Wagner. Records - c# reference - c#. 2024. URL: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record (visited on 07.03.2024). 

  8. Vincenzo Iannino, Claudio Mocci, Marco Vannocci, Valentina Colla, and Andrea Caputo. An event-driven agent-based simulation model for industrial processes. Applied Sciences, 10:4343, 06 2020. doi:10.3390/app10124343

  9. Bill Wagner. Extension methods - c# programming guide - c#. 2024. URL: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods (visited on 29.02.2024).