Important but less known features of C#

Asynchronous Streams: C# 8.0 introduced asynchronous streams, which allow you to return an IAsyncEnumerable from a method and consume it using the await foreach syntax. This can be useful for streaming data from a database or other source.

private async IAsyncEnumerable<WorkItemResponse> GetWorkItemDetails(int[] ids)
{
	var collection = await WorkItemTrackingHttpClient.GetWorkItemsAsync(
		projectName,
		ids,
		expand: Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemExpand.All)
		.ConfigureAwait(false);
	foreach (var item in wiCollection)
	{
		var fields = item.Fields;
		yield return new WorkItemResponse
		{
			WorkId = fields.ContainsKey(Constants.WorkId) ? fields[Constants.WorkId].ToString() : string.Empty
		};
	}
}

//calling 
var response = await GetWorkItemDetails(batch.ToArray()).ToListAsync();

Pattern Matching: C# 7.0 introduced pattern matching, which is a powerful feature that allows developers to test whether a value matches a pattern and extract its properties in a single operation.

object o = "Hello World"; 
if (o is string s) 
{ 
   Console.WriteLine(s.Length); // 11 
} 

Caller Information: C# 5.0 introduced the CallerFilePath, CallerLineNumber, and CallerMemberName attributes that allow developers to retrieve information about the caller’s file name, line number, and member name.

public void Log(
            string message, 
            [CallerFilePath] string file = "", 
            [CallerLineNumber] int line = 0, 
            [CallerMemberName] string member = "") 
{ 
   Console.WriteLine($"{file}:{line} {member}: {message}"); 
} 

Local Functions: C# 7.0 also introduced local functions, which are functions that are defined inside another function. These functions can access the outer function’s variables and parameters, making it easier to write small helper functions that are used only once.

public int Calculate(int a, int b) 
{ 
   int Add(int x, int y) => x + y; 
   return Add(a, b); 
}

Null-Conditional Operators: C# has introduced null-conditional operators ?. and ?[] to handle null references easily. These operators check whether an object is null or not before accessing its properties or calling its methods.

string name = person?.Name; // person is null-safe 
int? age = person?.Age; // age is nullable