Making a Console App Faster and Smaller with.NET 11

Even though a console application appears straightforward, putting it into production is quite another. Framework dependencies, NuGet packages, configuration files, and runtime components may all add to the deployment footprint of even modest applications.

When deploying command-line tools to numerous servers, cloud environments, containers, or CI/CD agents, this becomes crucial.

Think of a console program that calls an API, processes a file, runs every few minutes, and then exits. The setup and deployment overhead of an application can significantly increase its execution time, even if the fundamental business logic may only take a few milliseconds.

.NET provides several ways to address these problems. You can remove unused code through trimming, compile applications ahead of time using Native AOT, and configure publishing to prioritize application size or execution speed.

.NET 11 continues to build on these capabilities.

In this article, we will create a console application, examine its publishing options, and see how to make it smaller and faster without unnecessarily changing the application logic.

Why Does a Small Console Application Need Optimization?

Let’s take a simple example.

You have a console application responsible for processing daily transaction files. It starts, reads a file, performs calculations, writes the results, and exits.

The application itself may contain only a few classes, but its published output can include much more than your source code.

Some of the additional components include:

  • .NET runtime libraries.
  • Application dependencies.
  • Third-party NuGet packages.
  • Native libraries.
  • Configuration and supporting files.

The actual deployment size depends on the publishing model, target operating system, architecture, and dependencies.

There are also performance considerations.

If the application starts and exits frequently, startup time matters. If it runs continuously, steady-state execution performance may be more important.

This is why optimization should begin with understanding how the application is used.

Step 1: Create a Basic .NET 11 Console Application

First, create a new console application.

dotnet new console -n FastConsoleApp -f net11.0

cd FastConsoleApp

Open the project file. It should contain a configuration similar to this:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

</Project>

Now replace the contents of Program.cs with a small processing example.

using System.Diagnostics;

var stopwatch = Stopwatch.StartNew();

Console.WriteLine("Transaction processing started.");

var transactions = new[]
{
    1200.50m,
    2500.00m,
    850.75m,
    4200.00m,
    1750.25m
};

decimal total = transactions.Sum();

stopwatch.Stop();

Console.WriteLine($"Transactions: {transactions.Length}");
Console.WriteLine($"Total Amount: {total}");
Console.WriteLine($"Execution Time: {stopwatch.ElapsedMilliseconds} ms");

This application performs a small calculation and prints the result.

Build and run it:

dotnet build -c Release

dotnet run -c Release

At this point, we have a working application. Next, we will change how it is published.

Step 2: Understand the Different Publishing Options

.NET supports multiple deployment models. Each one makes a different trade-off between deployment size, startup performance, and runtime requirements.

Publishing option Main purpose Important consideration
Framework-dependent Uses an installed .NET runtime Smaller application-specific deployment
Self-contained Includes the required runtime Larger deployment output
Single-file Bundles application-dependent files Simplifies distribution
ReadyToRun Precompiles assemblies Can improve startup but increases size
Native AOT Compiles application code into native machine code Faster startup and potentially lower memory usage

These options are not interchangeable.

For example, single-file publishing does not automatically make an application faster or smaller. It primarily simplifies distribution.

Native AOT is different because it compiles application code into native machine code during publishing and removes the need for a JIT compiler at runtime.

For a small console application, Native AOT is worth testing.

Step 3: Enable Native AOT

Native AOT is one of the most useful options when your application needs fast startup and does not depend on runtime code generation.

To enable it, update the project file:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>

    <PublishAot>true</PublishAot>

  </PropertyGroup>

</Project>

The important setting is:

<PublishAot>true</PublishAot>

This tells the .NET SDK to use Native AOT when publishing the application.

Native AOT also enables trimming because the compiler needs to determine which code is required and which code can be removed.

Unlike traditional JIT-based applications, Native AOT does not generate machine code dynamically when the application starts.

Publish the application for a specific platform:

dotnet publish -c Release -r win-x64

For Linux:

dotnet publish -c Release -r linux-x64

For macOS on Apple Silicon:

dotnet publish -c Release -r osx-arm64

Replace the runtime identifier according to your deployment environment.

One important point: Native AOT requires the appropriate native compiler toolchain. Depending on the operating system, you may need Visual C++ build tools, Clang, or other platform-specific development tools.

Also, publish separately for each operating system and architecture.

Step 4: Reduce Application Size Using Trimming

Trimming removes code that static analysis determines is not required by your application.

For example, a project may reference a library containing dozens of features while using only a small portion of them.

Without trimming, unnecessary code can remain in the published application.

You can enable trimming for a compatible application using:

<PropertyGroup>
    <PublishTrimmed>true</PublishTrimmed>
    <TrimMode>full</TrimMode>
</PropertyGroup>

The full trimming mode allows the linker to remove unused members from assemblies where trimming is supported.

However, trimming requires attention.

Some libraries use reflection to discover classes, methods, or properties dynamically. The linker may not recognize these dependencies through static analysis.

As a result, required code could be removed from the published application.

If your application uses reflection, serialization, dependency injection, or dynamically loaded components, test the trimmed output carefully.

Do not enable full trimming in production without validating the application.

Step 5: Optimize for Size or Execution Speed

Another useful publishing option is OptimizationPreference.

.NET provides this setting to communicate whether the application should prioritize size or execution speed during Native AOT compilation.

For example, to prioritize a smaller executable:

<PropertyGroup>
    <PublishAot>true</PublishAot>
    <OptimizationPreference>Size</OptimizationPreference>
</PropertyGroup>

To prioritize execution speed:

<PropertyGroup>
    <PublishAot>true</PublishAot>
    <OptimizationPreference>Speed</OptimizationPreference>
</PropertyGroup>

The difference is important.

When you choose Size, the compiler favors a smaller native executable. Some execution performance may be affected.

When you choose Speed, the compiler prioritizes execution performance, potentially producing a larger binary.

There is no universal winner. The right choice depends on the workload.

For a short-lived command-line utility, startup and deployment size may matter more than peak throughput.

For a continuously running data-processing application, execution speed may be more important.

Step 6: Compare the Published Output

Do not assume that Native AOT has reduced your application size simply because it is enabled.

Publish the application using different configurations and compare the output.

For example:

dotnet publish -c Release -r win-x64 \
    --self-contained true

For Native AOT:

dotnet publish -c Release -r win-x64 \
    -p:PublishAot=true

For a single-file deployment:

dotnet publish -c Release -r win-x64 \
    -p:PublishSingleFile=true

Record the executable size and total deployment folder size.

You should also measure:

  • Application startup time.
  • Time taken to complete the actual workload.
  • Peak memory consumption.
  • CPU utilization.
  • Build and publishing time.

Remember that an optimized executable is not automatically the fastest application in every scenario.

Native AOT generally improves startup behavior and can reduce memory usage, but a JIT-compiled application may achieve better steady-state throughput for certain workloads.

Step 7: Validate the Application Before Production

This is the part developers should not skip.

A successful Native AOT publish does not guarantee that every application feature will work correctly.

For example, consider an application that dynamically loads assemblies:

var assembly = Assembly.LoadFrom("Plugin.dll");

This type of dynamic loading is not supported in the normal Native AOT deployment model.

Similarly, runtime code generation through Reflection.Emit is not supported.

If your application depends on these capabilities, you may need to redesign that functionality or use a different publishing model.

For JSON serialization, source generation is a useful approach because it makes serialization metadata available at compile time.

For example:

using System.Text.Json;
using System.Text.Json.Serialization;

public class Transaction
{
    public int Id { get; set; }

    public decimal Amount { get; set; }
}

[JsonSerializable(typeof(Transaction))]
internal partial class AppJsonContext
    : JsonSerializerContext
{
}

You can then serialize using the generated metadata:

var transaction = new Transaction
{
    Id = 101,
    Amount = 2500.50m
};

string json = JsonSerializer.Serialize(
    transaction,
    AppJsonContext.Default.Transaction);

Console.WriteLine(json);

This avoids depending on runtime reflection for the serialization metadata.

For production applications, test the actual published executable rather than relying only on tests executed through the development environment.

Common Mistakes to Avoid

Mistake Why it causes problems Better approach
Enabling Native AOT without checking dependencies Some packages are not AOT-compatible Review package compatibility
Assuming single-file means smaller Bundling can increase executable size Compare actual output sizes
Ignoring trimming warnings Required dynamically accessed members may be removed Resolve warnings and test published output
Publishing for the wrong architecture Native binaries are platform-specific Use the correct runtime identifier
Measuring only build time Does not represent application performance Measure startup and workload execution
Disabling globalization without checking requirements Date, number, and culture behavior may change Keep globalization support when needed

Advantages and Disadvantages

Advantages

  • Faster application startup through Native AOT.
  • Potentially lower memory consumption.
  • No separate .NET runtime installation required for Native AOT deployment.
  • Smaller deployment output in many suitable scenarios.
  • Easier distribution of command-line utilities.
  • Reduced dependency on runtime JIT compilation.

Disadvantages

  • Longer publishing and compilation times.
  • Platform-specific builds are required.
  • Dynamic loading and runtime code generation are restricted.
  • Some NuGet dependencies may not support Native AOT.
  • Trimming requires additional testing.
  • The resulting executable may be larger when optimization prioritizes execution speed.

Summary

.NET provides several ways to make console applications smaller and faster, but the best results come from choosing the right publishing strategy for the workload.

Native AOT is particularly useful for short-lived command-line tools, containerized utilities, and applications where startup time and memory consumption matter. Trimming can further reduce unnecessary code, while OptimizationPreference allows developers to prioritize size or execution speed.

However, these optimizations come with compatibility requirements. Applications that depend heavily on reflection, dynamic loading, or runtime code generation may require additional changes.

My recommendation is to start with a normal Release build, measure the application, enable Native AOT, and compare the published results. Keep the optimization only when the measurements demonstrate a real improvement.

The goal is not simply to produce a smaller executable. It is to make the application faster to start, easier to deploy, and more efficient to operate in production.

Best and Most Recommended ASP.NET Core 11.0 Hosting

Fortunately, there are a number of dependable and recommended web hosts available that can help you gain control of your website’s performance and improve your ASP.NET Core 11 web ranking. HostForLIFE.eu is highly recommended. In Europe, HostForLIFE.eu is the most popular option for first-time web hosts searching for an affordable plan. Their standard price begins at only €3.49 per month. Customers are permitted to choose quarterly and annual plans based on their preferences. HostForLIFE.eu guarantees “No Hidden Fees” and an industry-leading ’30 Days Cash Back’ policy. Customers who terminate their service within the first thirty days are eligible for a full refund.

By providing reseller hosting accounts, HostForLIFE.eu also gives its consumers the chance to generate income. You can purchase their reseller hosting account, host an unlimited number of websites on it, and even sell some of your hosting space to others. This is one of the most effective methods for making money online. They will take care of all your customers’ hosting needs, so you do not need to fret about hosting-related matters.