.NET is filled with powerful tools but not all them are well known among the developers probably because we haven’t read the full documentation of the .NET framework. So let me walk through you 10 of such features which I came across while trying to fix a bug or optimizing the performance.

1. Caller Info Attributes

Ever wondered how to log where a method was called from? .NET provides attributes like

  • [CallerMemberName] : Captures the name of the method/property that call the method it’s used in.
  • [CallerFilePath] : Retrieves the full path of the source file where the calling method is located.
  • [CallerLineNumber] : Retrieves the line number in the source file where the method is called.
public void LogMessage(string message,
[CallerMemberName] string memberName = "",
[CallerFilePath] string filePath = "",
[CallerLineNumber] int lineNumber = 0)
{
Console.WriteLine($"{message} from {memberName} in {filePath}
at line {lineNumber}");
}

Use Case : Debugging and logging frameworks

2. Using Span<T> and Memory<T> for High-Performance Code

.NET has high-performance types like Span<T> that let you work with slices of memory without allocations.

Span<byte> buffer = stackalloc byte[256];

Use case: Memory-efficient data processing in games or real-time systems.

3. Init-Only Setters (C# 9)

With C# 9, you can now create immutable objects using init instead of set. It allows you to set a property only during object initialization and not modify it later. Think of it as a set but only once.

public class User
{
public string Name { get; init; }
public string Email { get; init; }
}

var user = new User { Name = "Hansini", Email = "h@gmail.com" };

Use case: Creating DTOs or configuration models.

4. Pattern Matching Enhancements

C# pattern matching goes beyond switch statements. You can do:

if (obj is string { Length: > 5 } s)
{
Console.WriteLine($"Long string: {s}");
}

Use case: Cleaner type and property checks.

5. Target-Typed new() Expressions

No need to repeat type names on both sides!

List<string> names = new();

Use case: Cleaner, concise code.

6. using var for Cleaner Disposal

Since C# 8, you can now use using without braces, allowing tighter, cleaner syntax.

using var file = File.OpenRead("data.txt");

The file is disposed automatically when it goes out of scope.

Use case: Cleaner resource management for streams, connections, or any IDisposable.

7. File-Scoped Namespaces

Instead of curly braces for namespaces, you can now use a simpler format.

namespace MyApp.Models;

public class Product
{
// ...
}

Use case: Cleaner file layout.

8. Global Using Directives

Stop repeating common using statements across files by declaring them globally in a single file:

// GlobalUsings.cs
global using System;
global using System.Collections.Generic;

Use case: Especially useful for large solutions and shared projects.

9. Asynchronous Streams with await foreach

Iterate over asynchronous data sources easily:

await foreach (var item in GetItemsAsync())
{
Console.WriteLine(item);
}

Use case: Streaming data, file IO, APIs.

10. The with Expression for Object Cloning

Clone and modify immutable objects using with:

var user2 = user1 with { Email = "h@gmail.com" };

Use case: Immutable object manipulation, especially in record types.

Final Thoughts 💡

.NET is more than just a mature framework — it’s a toolbox filled with hidden gems. By mastering these lesser-known features, you can write code that’s more elegant, efficient, and modern.

Whether you’re building enterprise applications, APIs, or desktop tools, incorporating these tricks into your everyday development will boost your productivity and code quality.

Which of these features was new to you? Or do you have your own favorites? Share in the comments below!