Free Ebook cover C# Essentials: Building Console Apps with .NET

C# Essentials: Building Console Apps with .NET

New course

12 pages

Understanding .NET Runtime Basics for Console Apps

Capítulo 2

Estimated reading time: 8 minutes

+ Exercise

Source Code, Compiled Output, and Execution

When you run a console app, you are not directly running your source code (.cs files). Source code is human-readable instructions that must be transformed into something the computer can execute.

1) Source code (.cs)

This is what you write: classes, methods, and statements. The C# compiler does not execute this text; it compiles it.

2) Compiled output (assembly)

Compilation produces an assembly, typically a .dll for a console app (and sometimes an .exe wrapper depending on settings). The assembly contains:

  • IL (Intermediate Language): CPU-independent instructions.
  • Metadata: information about types, methods, references, and attributes.
  • References: which other assemblies your code depends on.

You can think of IL as a portable “recipe” that still needs the runtime to turn it into machine code for your specific OS/CPU.

3) Execution (runtime + JIT)

At execution time, the .NET runtime loads your assembly, resolves dependencies, and uses a JIT (Just-In-Time) compiler to translate IL into machine code as needed. Some builds can also use ahead-of-time compilation, but the key mental model is: the runtime is responsible for turning the compiled output into running code on your machine.

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

What the .NET Runtime Does When Starting a Console App

To reason about runtime behavior, it helps to know the typical startup sequence. The exact details vary by OS and project settings, but the core steps are consistent.

High-level startup sequence

  • Host starts: an app host (or the dotnet host) is invoked to launch your app.
  • Runtime selection: the host chooses a compatible .NET runtime based on your app’s configuration (target framework and runtime settings).
  • Runtime initializes: the CLR (Common Language Runtime) sets up garbage collection, threading, exception handling, and other services.
  • Assembly load: your entry assembly is loaded; referenced assemblies are located and loaded as needed.
  • Entry point runs: the runtime calls your entry point (Main or top-level statements).
  • JIT compilation: methods are compiled from IL to machine code when first executed (in the common JIT model).
  • Execution continues: your code runs, allocating objects, calling libraries, and interacting with the OS through .NET APIs.
  • Process ends: when the entry point returns (or Environment.Exit is called), the process terminates and the runtime cleans up.

Practical walkthrough: what to look at when an app “won’t run”

If a console app fails at startup, you can often narrow it down by checking these in order:

  • Is the correct runtime installed? A mismatch between the app’s target framework and installed runtimes can prevent startup.
  • Is the entry assembly present? Missing output files in bin can indicate a build/publish issue.
  • Are dependencies resolvable? Missing or incompatible referenced assemblies can cause load failures.
  • Is configuration correct? Debug/Release differences can change behavior and performance characteristics.

Configuration Basics: Debug vs Release

Build configuration affects how the compiler and tooling produce output. The runtime still executes IL (and JITs it), but the shape of the output and the debugging experience can differ significantly.

Debug configuration

  • Optimizations are reduced to make debugging predictable (stepping through code matches source more closely).
  • More debugging information is produced (symbol files such as .pdb) to map runtime execution back to source lines.
  • Performance is not the priority; you may see slower execution and different inlining behavior.

Release configuration

  • Compiler optimizations are enabled to improve performance.
  • Debugging experience changes: stepping may skip lines due to inlining and reordering.
  • Output is intended for distribution and realistic performance testing.

Practical step-by-step: verify which configuration you are running

  • Build the project in Debug and inspect the output folder (typically under bin/Debug/<tfm>/).
  • Build the project in Release and inspect the output folder (typically under bin/Release/<tfm>/).
  • Notice that both contain your main assembly, but the presence and role of symbol files (.pdb) and the runtime behavior during debugging can differ.

Where Dependencies Come From (and How the Runtime Finds Them)

A console app rarely runs as a single file with no external code. Dependencies are the libraries your app needs at runtime, and they typically come from a few places.

1) The .NET shared framework

Many core libraries are provided by the installed .NET runtime (the shared framework). Your app references them, and the host/runtime loads the appropriate versions that match your target framework and runtime selection rules.

2) NuGet packages

When you add a package reference, build tooling restores it into a global package cache and then includes the necessary assemblies (or references to them) in your build output and dependency manifest. At runtime, the app loads the package assemblies from the output location (or from a publish layout).

3) Project references

If your solution has multiple projects, one project can reference another. The referenced project is built into an assembly, and that assembly becomes a dependency of the app.

4) Direct file references (less common)

You can reference a local .dll directly. This can work, but it requires careful versioning and deployment because the runtime must be able to locate that file at execution time.

Dependency resolution mental model

  • Compile-time: the compiler needs references to resolve types and methods.
  • Runtime: the runtime needs the actual assemblies available to load. If an assembly is missing or incompatible, you can get load exceptions at startup or when a code path is first executed.

Guided Walkthrough: Reading the Project File Settings That Affect Runtime Behavior

The project file (.csproj) is a key source of truth for how your app is built and what runtime it expects. Understanding a few settings helps you reason about compatibility and startup behavior.

Step-by-step: open and identify the key properties

In your project, open the .csproj file and look for a <PropertyGroup> containing settings like these:

<Project Sdk="Microsoft.NET.Sdk">  <PropertyGroup>    <OutputType>Exe</OutputType>    <TargetFramework>net8.0</TargetFramework>    <ImplicitUsings>enable</ImplicitUsings>    <Nullable>enable</Nullable>  </PropertyGroup></Project>

OutputType: why it matters

<OutputType>Exe</OutputType> indicates the project produces an executable entry point (a console app). Even when the main compiled assembly is a .dll, the app is still treated as an executable application with an entry point.

TargetFramework: the runtime compatibility anchor

<TargetFramework>net8.0</TargetFramework> (often abbreviated as TFM) is one of the most important runtime-related settings. It communicates:

  • Which API surface you can use at compile time (what libraries and members are available).
  • Which runtime family/version is required at execution time (what installed runtime can run it).
  • Which dependency assets are selected from NuGet packages (packages often ship different builds for different TFMs).

What TargetFramework means for compatibility

  • Running the app: the machine must have a compatible .NET runtime installed. If the required runtime is missing, the host typically fails with an error indicating which runtime is needed.
  • Using libraries: a library built for a newer TFM may not be usable from an older-targeting app. Conversely, libraries targeting broader compatibility (for example, older TFMs) are often usable from newer apps.
  • NuGet selection: when restoring packages, NuGet picks the best-matching assets for your TFM. If no compatible assets exist, restore/build can fail or fall back in ways that may limit functionality.

Practical step-by-step: change TargetFramework and observe effects

  • In the .csproj, change <TargetFramework>net8.0</TargetFramework> to another TFM you have installed (for example, net7.0).
  • Build the project and note whether compilation succeeds; failures often indicate API usage not available in the chosen TFM.
  • Run the project and note whether the runtime is found; failures often indicate the required runtime is not installed.
  • Restore/build again after the change and observe that package resolution may change because NuGet selects assets based on the TFM.

Other project settings that commonly influence runtime behavior

Depending on your project, you may also see settings like these:

  • RuntimeIdentifier / RuntimeIdentifiers: influences which OS/CPU runtime assets are used when publishing for a specific platform.
  • SelfContained: controls whether publishing bundles the runtime with your app (self-contained) or relies on an installed shared runtime (framework-dependent).
  • LangVersion: affects which C# language features are allowed at compile time (not the runtime itself, but it changes what code can be produced).
  • DefineConstants: affects conditional compilation, which can change what code exists in Debug vs Release builds.

When diagnosing runtime issues, start with TargetFramework and dependency references, then check publish-related settings if the issue only appears after publishing or on another machine.

Now answer the exercise about the content:

A console app builds successfully, but it fails immediately on another machine. Based on how .NET selects and starts the runtime, what is the most likely first thing to verify?

You are right! Congratulations, now go to the next page

You missed! Try again.

Startup relies on a host selecting a compatible runtime based on the app’s configuration (including TargetFramework). If the required runtime isn’t installed, the app can fail before loading assemblies or running the entry point.

Next chapter

C# Types, Variables, and Console I/O

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.