CSharp - Other

Operator

Arithmetic Operators

Used for mathematical calculations.

+ : Addition
- : Subtraction
* : Multiplication
/ : Division
% : Modulus (remainder after division)
++ : Increment
-- : Decrement


Relational (Comparison) Operators

Used to compare two values.

== : Equal to
!= : Not equal to
> : Greater than
< : Less than
>= : Greater than or equal to
<= : Less than or equal to


Logical Operators

Used to perform logical operations.

&& : Logical AND
|| : Logical OR
! : Logical NOT


Bitwise Operators

Used for manipulating data at the bit level.

& : Bitwise AND
| : Bitwise OR
^ : Bitwise XOR (exclusive OR)
~ : Bitwise NOT (inversion)
<< : Left shift
>> : Right shift


Assignment Operators

Used to assign values to variables.

= : Assignment
+= : Add and assign
-= : Subtract and assign
*= " Multiply and assign
/= : Divide and assign
%= : Modulus and assign
&= : Bitwise AND and assign
|= : Bitwise OR and assign
^= : Bitwise XOR and assign
<<= : Left shift and assign
>>= : Right shift and assign


Null-Coalescing Operators

Handle null values in expressions.

?? : Null-coalescing operator (returns the left operand if it's not null, otherwise the right)
??= : Null-coalescing assignment (assigns the right operand only if the left operand is null)


Conditional (Ternary) Operator

Used to return one of two values depending on a condition.

?: : Ternary operator (condition ? first_expression : second_expression)


Type Operators

Used for working with types and type casting.

is : Checks if an object is compatible with a given type
as : Attempts to cast an object to a type, returns null if the cast fails
typeof : Returns the Type of a class or struct
sizeof : Returns the size in bytes of a value type
checked : Enables overflow checking for arithmetic operations
unchecked : Disables overflow checking for arithmetic operations


Index and Range Operators

[] : Access elements of an array or indexer
^ : Index from the end (C# 8.0+)
.. : Range operator (C# 8.0+)


Lambda Operator

Used in lambda expressions.

=> : Lambda operator


Member Access and Object Operators

Used to access members of objects and handle null reference types.

. : Member access operator (e.g., object.Property)
-> : Pointer-to-member access (unsafe context)
?. : Null-conditional operator (invokes a member only if the object isn't null)
[] : Index access


Pointer Operators (Unsafe Code)

Used in unsafe contexts for working with pointers.

* : Dereference pointer
& : Address-of operator (returns a pointer to the variable)
-> : Pointer member access
[] : Pointer index access


Other Special Operators

new : Creates objects and invokes constructors
sizeof : Returns the size in bytes of a value type
typeof : Returns the type of a class
checked : Enables overflow checking for arithmetic operations
unchecked : Disables overflow checking for arithmetic operations
await : Pauses an async method until a task is complete
yield : Returns values from an iterator method
default : Returns the default value for a type
nameof : Returns the name of a variable, type, or member as a string


Delegate and Event Operators

Used to work with delegates and events.

+= : Add a method to a delegate or event
-= : Remove a method from a delegate or event

Optimize Start-Up Performance in .NET Core

Optimize Garbage Collection (GC)

Tune the garbage collection settings for your application based on its memory usage and workload.


For server workloads, enable server GC for better performance with multi-threaded applications

<PropertyGroup>
    <ServerGarbageCollection>true</ServerGarbageCollection>
    <ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
    <RetainVMGarbageCollection>true</RetainVMGarbageCollection>
    <TieredCompilation>true</TieredCompilation>
    <TieredCompilationQuickJit>true</TieredCompilationQuickJit>
</PropertyGroup>


GCSettings.LatencyMode: For latency-sensitive applications (e.g., web APIs), you can configure the latency mode to minimize the impact of garbage collection during startup.

GCSettings.LatencyMode = GCLatencyMode.LowLatency;


Optimize Razor Pages and Views (For ASP.NET Core)

If you’re building a web application using Razor Pages or Views, the compilation of Razor files can introduce startup delays.


Precompile Razor Views: Precompile your Razor views to avoid runtime compilation during startup.


Add this to your .csproj file:

<PropertyGroup>
    <RazorCompileOnPublish>true</RazorCompileOnPublish>
</PropertyGroup>


This ensures that views are compiled at build or publish time, reducing startup time.


Reduce the Size of the Application Binary

The larger your application, the more time it takes to load the assemblies.


Trim Unused Libraries: Use ILLinker (available in .NET Core) to trim unused code, removing unnecessary parts of libraries that aren’t used by your application. Add this to your .csproj:

<PropertyGroup>
    <PublishTrimmed>true</PublishTrimmed>
</PropertyGroup>


This feature analyzes dependencies and removes unused parts, reducing the application size and improving startup time.


Single-File Publish: In .NET Core 3.0+ and .NET 5/6/7/8, you can publish your application as a single-file executable to avoid the overhead of loading multiple assemblies at startup.


Add this to your .csproj:

<PropertyGroup>
    <PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>


Use Ahead-of-Time (AOT) Compilation

.NET 7 and .NET 8 offer Native AOT (Ahead-of-Time Compilation), which compiles .NET applications directly into machine code, eliminating the need for JIT (Just-In-Time) compilation during startup.


Native AOT: For applications where startup performance is critical, you can compile your .NET code ahead of time into native code.


Add this to your .csproj:

<PropertyGroup>
    <PublishAot>true</PublishAot>
</PropertyGroup>


This feature greatly improves startup performance by reducing the time spent on JIT compilation.


Use HTTP/2 and Enable Response Compression

If your .NET Core application serves HTTP traffic, enabling HTTP/2 and response compression can improve the startup performance from the user’s perspective.


HTTP/2: HTTP/2 reduces the number of connections and allows multiplexing of requests. Enable it in Program.cs:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(IPAddress.Any, 5000, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
    });
});


Response Compression: Use response compression to decrease the size of payloads sent to the client, reducing startup time over the network.

public void ConfigureServices(IServiceCollection services)
{
    services.AddResponseCompression();
}


public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseResponseCompression();
}


Use HTTP/3

HTTP/3 reduces latency and enhances startup performance for web applications with its improved connection model over HTTP/2. .NET 6 and later versions support HTTP/3.


Enable HTTP/3 by configuring Kestrel:

builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Listen(IPAddress.Any, 5001, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
    });
});


HTTP/3 offers faster connection establishment, which improves the perceived startup performance from the client’s perspective.


Use Ready-to-Run (R2R) Compilation

Ready-to-Run (R2R) is a feature that pre-compiles your .NET assemblies into native code at build time, reducing the time spent on JIT compilation.


Enable R2R in your .csproj file:

<PropertyGroup>
    <PublishReadyToRun>true</PublishReadyToRun>
</PropertyGroup>


This can significantly improve startup times, especially for large applications, by eliminating the need for JIT compilation at runtime.


Optimizing Performance using Efficient LINQ queries

Combining Where and FirstOrDefault()

Problem: Using Where and then FirstOrDefault results in redundant filtering.

//BAD code
var user = collection.Where(x => x.Id == id).FirstOrDefault();


// Efficient Code
var user = collection.FirstOrDefault(x => x.Id == id);


Efficient Contains Usage

Problem: Checking for existence in lists can be slow.

// Bad code
var result = collection.Where(x => list.Contains(x.Id)).ToList();

// Efficient code
var set = new HashSet<int>(list);
var result = collection.Where(x => set.Contains(x.Id)).ToList();


Efficient Distinct Usage

Problem: Using Distinct on large datasets can be slow.

var result = collection.Select(x => x.Name).Distinct().ToList();


//Efficient code
var result = collection.Select(x => x.Name).ToHashSet();


Using LongCount for Large Collections

Problem: Count on very large collections can overflow.

var count = collection.LongCount();

Parse Numbers Faster from a String

Before

var line = "42, 84, 126";
var parts = line.Split(',').Select(int.Parse).ToArray();


After

ReadOnlySpan<char> span = line.AsSpan();
while (span.Length > 0)
{
    var commaIndex = span.IndexOf(',');
    ReadOnlySpan<char> token;
    if (commaIndex == -1)
    {
        token = span;
        span = ReadOnlySpan<char>.Empty;
    }
    else
    {
        token = span.Slice(0, commaIndex);
        span = span.Slice(commaIndex + 1);
    }
    if (int.TryParse(token.Trim(), out var value))
    {
        // Do something with value
    }
}

Perform a computationally intensive operation on a large collection of items in LINQ

AsParallel() in C# is a method in the Parallel LINQ (PLINQ) library, which is a parallel implementation of LINQ (Language Integrated Query). PLINQ enables you to perform queries in parallel, utilizing multiple processors or cores to speed up query execution.

using System;
using System.Linq;


class Program
{
    static void Main()
    {
        // An array of numbers
        int[] numbers = Enumerable.Range(1, 1000000).ToArray();


        // Using AsParallel to enable parallel processing
        var evenNumbers = numbers.AsParallel()
                                 .Where(n => n % 2 == 0)
                                 .ToArray();


        Console.WriteLine($"Found {evenNumbers.Length} even numbers.");
    }
}


If order is important, you can use the AsOrdered() method after AsParallel() to maintain the original order of the elements in the output. However, this might reduce the performance benefits.

var evenNumbers = numbers.AsParallel()
                         .AsOrdered()
                         .Where(n => n % 2 == 0)
                         .ToArray();


You can control the degree of parallelism using the WithDegreeOfParallelism method.

var evenNumbers = numbers.AsParallel()
                         .WithDegreeOfParallelism(4) // Limits to 4 concurrent threads
                         .Where(n => n % 2 == 0)
                         .ToArray();


When to Use AsParallel()

  • You have a large dataset.
  • The operations are CPU-bound (intensive computations).
  • Order of execution is not critical.
  • You have multiple cores or processors available.

Performance-Optimized Object Validation

public static class ValidationExtensions
{
    public static bool IsValidEmail(this string email)
    {
        if (string.IsNullOrWhiteSpace(email)) return false;
        
        try
        {
            var addr = new MailAddress(email);
            return addr.Address == email;
        }
        catch
        {
            return false;
        }
    }
    
    public static bool IsValidPhoneNumber(this string phoneNumber)
    {
        if (string.IsNullOrWhiteSpace(phoneNumber)) return false;
        
        // Remove all non-numeric characters
        var cleaned = new string(phoneNumber.Where(char.IsDigit).ToArray());
        
        // Check if it's between 10-15 digits (international standard)
        return cleaned.Length >= 10 && cleaned.Length <= 15;
    }
    
    public static bool IsInRange(this int value, int min, int max)
    {
        return value >= min && value <= max;
    }
    
    public static bool IsInRange(this decimal value, decimal min, decimal max)
    {
        return value >= min && value <= max;
    }
    
    public static ValidationResult ValidateRequired<T>(this T value, string fieldName) where T : class
    {
        return value != null ? 
            ValidationResult.Success : 
            new ValidationResult($"{fieldName} is required");
    }
}

public class ValidationResult
{
    public bool IsValid { get; }
    public string ErrorMessage { get; }
    
    private ValidationResult(bool isValid, string errorMessage = null)
    {
        IsValid = isValid;
        ErrorMessage = errorMessage;
    }
    
    public static ValidationResult Success => new ValidationResult(true);
    public static ValidationResult Failure(string errorMessage) => new ValidationResult(false, errorMessage);
    
    public ValidationResult(string errorMessage) : this(false, errorMessage) { }
}

Pivot Tables: Transforming Row Data to Columns

Create pivot tables to transform row-based data into column-based summaries:

var sales = new List<Sale>
{
    new Sale { Product = "Laptop", Quarter = "Q1", Amount = 1000 },
    new Sale { Product = "Laptop", Quarter = "Q2", Amount = 1200 },
    new Sale { Product = "Mouse", Quarter = "Q1", Amount = 200 },
    new Sale { Product = "Mouse", Quarter = "Q2", Amount = 300 }
};

// Create a pivot table
var pivot = sales
    .GroupBy(s => s.Product)
    .Select(g => new {
        Product = g.Key,
        Q1 = g.Where(s => s.Quarter == "Q1")
            .Sum(s => s.Amount),
        Q2 = g.Where(s => s.Quarter == "Q2")
            .Sum(s => s.Amount),
        Total = g.Sum(s => s.Amount)
    });

// Results:
// Product  Q1    Q2    Total
// Laptop   1000  1200  2200
// Mouse    200   300   500

Predicate Delegate

Predicate is the delegate like Func and Action delegates. It represents a method containing a set of criteria and checks whether the passed parameter meets those criteria. A predicate delegate methods must take one input parameter and return a Boolean - true or false.


The Predicate delegate is defined in the System namespace, as shown below:


Predicate signature:

public delegate bool Predicate<in T>(T obj);


Same as other delegate types, Predicate can also be used with any method, anonymous method, or lambda expression.


Example:

Predicate delegate

static bool IsUpperCase(string str)
{
  return str.Equals(str.ToUpper());
}

static void Main(string[] args)
{
  Predicate<string> isUpper = IsUpperCase;

  bool result = isUpper("hello world!!");

  Console.WriteLine(result);
}

Output: false


Predicate delegate with anonymous method

static void Main(string[] args)
{
  Predicate<string> isUpper = delegate(string s) { return s.Equals(s.ToUpper());};
  bool result = isUpper("hello world!!");
}

Output: false


Predicate delegate with lambda expression

static void Main(string[] args)
{
  Predicate<string> isUpper = s => s.Equals(s.ToUpper());
  bool result = isUpper("hello world!!");
}

Output: false


Prefer for each loop over for loops when iterating through collections

Do

int[] numbers = { 1, 2, 3, 4, 5 };
foreach (int number in numbers)
{
    Console.WriteLine(number);
}


Don't

int[] numbers = { 1, 2, 3, 4, 5 };
for (int i = 0; i < numbers.Length; i++)
{
    Console.WriteLine(numbers[i]);
}

Prepend

Prepend is used to add a single element to the beginning of a sequence.


If you want to prepend a product to the beginning of the list:

var products = new List<string> { "Product1", "Product2", "Product3" };
var newProduct = "Product0";


var updatedProducts = products.Prepend(newProduct);


foreach (var product in updatedProducts)
{
    Console.WriteLine(product);
}

Publish an ASP.NET Core app to IIS

  1. Install the .NET Core Hosting Bundle on Windows Server.
  2. Create an IIS site in IIS Manager.
  3. Deploy an ASP.NET Core app.

Rate Limiting in ASP.NET Core

Program.cs

var builder = WebApplication.CreateBuilder(args);


// Configure rate limiter
builder.Services.AddRateLimiter(options =>
{
    options.AddFixedWindowLimiter("Fixed", opt =>
    {
        opt.PermitLimit = 10;           // Maximum 10 requests
        opt.Window = TimeSpan.FromMinutes(1); // In a 1-minute window
    });
});


Rate-Limiting Strategies


Fixed Window

Limits requests in fixed time intervals.

options.AddFixedWindowLimiter("Fixed", opt =>
{
    opt.PermitLimit = 5;
    opt.Window = TimeSpan.FromSeconds(30);
});


Sliding Window

Adjusts limits dynamically based on the sliding window.

options.AddSlidingWindowLimiter("Sliding", opt =>
{
    opt.PermitLimit = 10;
    opt.Window = TimeSpan.FromMinutes(1);
    opt.SegmentsPerWindow = 4;
});


Concurrency Limiting

Restricts the number of concurrent requests.

options.AddConcurrencyLimiter("Concurrent", opt =>
{
    opt.PermitLimit = 5; // Maximum concurrent requests
});


Token Bucket

Uses tokens to track available requests.

options.AddTokenBucketLimiter("TokenBucket", opt =>
{
    opt.TokenLimit = 10;
    opt.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
    opt.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
    opt.TokensPerPeriod = 1;
    opt.QueueLimit = 5;
});