Zmena procesu

My favorite C# features in real world use cases

The article presents my personal take on some of my favorite C# language features, focusing on real-world usage patterns found in a large code base. It provides practical examples of how these features are used by my team in actual development with links to official documentation.
Csharp I

13. March 2025  |  Tech Insights by Pavol Mráz  |  CTO: My favorite C# features in real world use cases

Introduction

I have read a ton of articles about someone’s favorite C# language features on the Internet (especially on medium.com) and some of them are really good and inspiring. I have decided that I might add two cents to the discussion, and this is my take on the subject. It is a little bit different than the others, because I present real-world usage patterns, stemmed from our large code base (980+ KLOC) that is undergoing continuous development for more than nine years now (January, 2025). The code base is kind of a modular monolith comprised of several business applications, APIs, as well as some reusable functionality in the form of NuGet packages.

In the discussion and code examples below, I have renamed some of the code artifacts, but the actual usage patterns remained intact. I have not tried to restate what the official documentation says, instead, I am providing links to its relevant sections. I emphasize here that these are my subjective opinions, and not all my colleagues agree with me 100%.

Field keyword

The contextual field keyword can be used in property accessors to access the compiler-generated backing field of a property. We have been using it mostly to add validation to property setters implemented originally as auto-properties. For example, we had this code before introducing the field keyword:

class Person
{
 public string Name { get; set; }
}

After gradually introducing nullability to our code base, we were able to add property setter validation easily using the field keyword like this:

class Person
{
 public string Name { get; set { field = value ?? throw new ArgumentNullException(nameof(value)); } } = "";
}

Please note that the official documentation says that:

“The field keyword is a preview feature in C# 13. You must be using .NET 9 and set your <LangVersion> element to preview in your project file in order to use the field contextual keyword.”

However, we have been using it in projects targeting netstandard2.0 without problems just with the <LangVersion>preview</LangVersion> property set in .csproj (or Directory.Build.props file). This is understandable, because the field keyword is just a syntactic sugar allowing us to access the backing field without needing to know the actual compiler-generated mangled name as this gist from sharplab.io illustrates.

Bonus chatter: First time I have used this feature, I wrote the code this way (note the property name in the setter):

class Person
{
 public string Name { get; set { Name = value ?? throw new ArgumentNullException(nameof(value)); } } = "";
}

Neither the compiler nor any of the analyzers warned me, so I realized the mistake only after running the program and getting an StackOverflowException upon the very first Name property assignment. Now it is obvious that referencing the Name property inside the property setter method creates infinite recursion and I honestly do not know, what led me to write it that way.

Pattern matching and switch expressions

This feature was first introduced in C# version 7.0 but since then, it has been gradually enhanced in almost all subsequent releases, including switch expressions in C# version 8.0. We have been using it mostly when working with enums, because the compiler ensures that we always cover all possible enum values (the switch expression is exhaustive as they call it). All but one of the usage patterns I have found use constant pattern matching mapping from enum to some other value. For example, mapping from MqttNetLogLevel (from the excellent MQTTnet library) to Microsoft’s standard LogLevel:

using Microsoft.Extensions.Logging;
using MQTTnet.Diagnostics;
...
LogLevel netLogLevel = logLevel switch
{
 MqttNetLogLevel.Error => LogLevel.Error,
 MqttNetLogLevel.Warning => LogLevel.Warning,
 MqttNetLogLevel.Info => LogLevel.Information,
 MqttNetLogLevel.Verbose => LogLevel.Trace,
 _ => LogLevel.Debug
};

The exhaustive mapping can be illustrated by omitting the last line with the discard expression “_ => LogLevel.Debug”. The then complains with a clear explanation: error CS8524: The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. For example, the pattern ‘(MQTTnet.Diagnostics.MqttNetLogLevel)4’ is not covered.

The only other switch expression not using the constant pattern over enum, but based on a relational pattern matching expression I found in the code base was in a MAUI Android application. The application uses GPS for location tracking and maps the horizontal location accuracy from meters to application-defined enumeration like this:

public enum GpsLocationQuality
{
  Unknown = 0,
  Poor = 1,
  Good = 2,
  Excellent = 3
}
...
Android.Locations.Location location = <get location from ILocationListener>
GpsLocationQuality quality = location.Accuracy switch
{
  <= 0 => GpsLocationQuality.Unknown,
  < 5 => GpsLocationQuality.Excellent,
  <= 15 => GpsLocationQuality.Good,
  _ => GpsLocationQuality.Poor
};

Please note that when we omit the last discard expression, the compiler still enforces the exhaustive mapping by giving us the following error:

error CS8509: The switch expression does not handle all possible values of its input type (it is not exhaustive). For example, the pattern '15.000001F' is not covered

Raw string literals

The Raw string literals feature was introduced in C# 11 and I did find just one use case in our code base that I have actually implemented myself 😊. It was as a portion of integration test suite for legacy SOAP asmx API that was migrated to ASP.NET Core (using the excellent SoapCore package). I have used a testing approach similar to what Michael feathers calls “characterization testing” in his excellent book “Working Effectively with Legacy Code”:

  • I have captured the response XML payloads returned by invoking the legacy SOAP endpoints over a seed database.
  • I have written tests that invoked the new ASP.NET Core endpoints (over the same seed database) and compared the XML payloads with the captured legacy ones.

For the payload comparison, I have used the XMLUnit.Core package and the legacy response I have put directly into the test code using raw string literals like this:

[Fact]
public async Task Verify_SomeService_response()
{
  string expectedXmlResponse =
"""
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<endOfDayResponse
xmlns="http://example.com/common/service/types/SomeService/1.0">
      <manifestPDF
            xmlns="">some value
      </manifestPDF>
</endOfDayResponse>
</soap:Body>
</soap:Envelope>
""";
  string soapCoreXmlResponse = await InvokeSoapCoreServiceAsync(<hardcoded parameters>);
  var diff = DiffBuilder
     .Compare(expectedXmlResponse)
     .WithTest(soapCoreXmlResponse)
     .IgnoreWhitespace()
     .Build();
   Assert.False(diff.HasDifferences());

By using raw string literals, I was able to copy the legacy SOAP payloads directly from Postman response bodies and paste them into the test methods verbatim.

Records, init properties, deconstruction

The records feature introduced in C# 9.0 we have been using almost exclusively as a convenient way to define data transfer objects. The automatic read-only property declaration when using records with primary constructors makes them very convenient and terse (and elegant IMHO):

public record class GrantAccessTokenRequestModel(
   string UserName,
   string Password,
   string ClientId);

The declaration creates an immutable record type and with the help of deconstruction using the with keyword, we can create clones at will:

var requestModel = new GrantAccessTokenRequestModel(
    UserName: form[RequestFormKeys.UserName],
    Password: form[RequestFormKeys.Password],
    ClientId: form[RequestFormKeys.ClientId]);
var clone = requestModel with { Password = "********" };

In order to customize serialization of these record instances, we tend to do that by appropriate application of the JsonPropertyNameAttribute, along with  init and required C# attributes, for example:

  public record class GrantAccessTokenResponseModel
  {
    [JsonPropertyName("access_token")]
    public required string AccessToken { get; init; }
    [JsonPropertyName("refresh_token")]
    public required string RefreshToken { get; init; }
    [JsonPropertyName("expires_in")]
    public required int ExpiresInMinutes { get; init; }
    [JsonPropertyName("token_type")]
    public string TokenType { get; } = "Bearer";
  }

The above declaration ensures proper serialization to JSON using the specified JSON property names. The declaration also states that the AccessToken, RefreshToken and ExpiresInMinutes properties must be initialized upon construction, for example:

var response = new GrantAccessTokenResponseModel()
 {
   AccessToken = accessTokenString,
   ExpiresInMinutes = (int)(expirationTimeUtc - DateTime.UtcNow).TotalMinutes,
   RefreshToken = refreshToken
 };
var response = new GrantAccessTokenResponseModel()
 {
   AccessToken = accessTokenString,
   ExpiresInMinutes = (int)(expirationTimeUtc - DateTime.UtcNow).TotalMinutes,
   RefreshToken = refreshToken
 };

Please note that we use the record class declaration with the class keyword to make it explicit that we are declaring a reference type (not the record struct value type).

Author: Pavol Mráz  |  Chief Technology Officer:

Throughout his professional career, he has contributed to the design, development, and maintenance of customer information systems for both private and public sector companies, as well as corporate tools and methodologies aimed at streamlining software development. 

Examples include the service software for the voting system of the Slovak Parliament, the system for tracking technical changes for Volkswagen Slovakia, and the corporate authorization framework Constable.

blog

Related posts

Kedy je čas hodiť starý back-office systém oknom?
Novinky a aktualizácieInterný systémMicrosoft 365SoftvérZmena procesu
Rok 2025 naprieč oddeleniami Všetko, čo nás preverilo, naučilo a posilnilo
Novinky a aktualizácieZamestnanciZo života firmy
Keď efektivita stretne kreativitu Spoluorganizovali sme AI workshop s AMCHAM
Novinky a aktualizácieAIAI FORESEXTRACTORZo života firmy

Get in touch with us

Do you have an idea that you want to implement or a problem that needs solution? Do you want to focus on your business and not the IT supporting it? Get in touch with us and let‘s do great things together.

Information on the processing of personal data - contact form

If you use our contact form, we will process your personal data to the extent of the data you have provided in the form (name and surname, contact details, or link to the company if you are the contact person of a legal entity or other data specified in your message) for the purpose of handling the request for information (inquiry/question) submitted via the contact form on the website of the controller.

In accordance with Regulation (EU) 2016/679 of the European Parliament and of the Council of 27 April 2016 on the protection of natural persons with regard to the processing of personal data and on the free movement of such data and repealing Directive 95/46/EC (General Data Protection Regulation) (hereinafter also referred to as the “GDPR”), we hereby inform you about the terms and conditions of the processing of your personal data. 

Identification data of the controller: the company ForesServices, s. r. o., with registered office: Prievozská 14, Bratislava 821 09, ID No.: 35692103, registered in the Commercial Register of the Municipal Court Bratislava III, Section: Sro, Insert No.: 11155/B

Contact details of the controller:


We process data for this purpose on the basis of your consent in order to respond to your enquiry or request.
The provision of data is voluntary, but without providing it, the request cannot be processed. The data subject has the right to withdraw his or her consent at any time by sending an e-mail to the e-mail address of the controller info@fores.group.

Withdrawal of consent does not affect the lawfulness of processing based on consent prior to its withdrawal. We will store your data for this purpose until the request is processed (until the requested information is provided), but for no longer than 1 year. The recipient of your data is the provider of support and operation of the website and the provider of web hosting services. Personal data will not be used for automated individual decision-making, including profiling.


As a data subject, you have the following additional rights:  

The right of access to personal data under Article 15 GDPR: 

The data subject shall have the right to obtain confirmation that the controller processes personal data concerning him or her. The data subject has the right to obtain access to his or her personal data (the right to be provided with a copy of the personal data held by the controller about the data subject) and information about how the controller processes it, to the extent provided for in Article 15 of the GDPR. 

The right to rectification of personal data pursuant to Article 16 GDPR: 

The data subject shall have the right to have personal data concerning him or her rectified if it is incorrect or to have it completed if it is incomplete.  

The right to erasure (right to be forgotten) under Article 17 of the GDPR: 

The data subject shall have the right to obtain from the controller the erasure of personal data concerning him or her without undue delay, under the conditions set out in Article 17 of the GDPR. This right of the data subject shall be assessed by the controller in the light of all the relevant circumstances in accordance with Article 17 GDPR. 

The right to data portability under Article 20 GDPR: 

Where the processing is based on consent or on a contract and is carried out by automated means, the data subject shall have the right to obtain his or her personal data which he or she has provided to the controller in a structured, commonly used and machine-readable format and shall have the right to transmit those data to another controller. As far as technically feasible, he or she shall have the right to have the data transmitted directly from one controller to another.

The right to restrict the processing of personal data pursuant to Article 18 GDPR: 

The data subject shall have the right to have the controller restrict the processing of his or her personal data if one of the cases referred to in Article 18 of the GDPR occurs (e.g. if the data subject contests the accuracy of the personal data during the period for verifying their accuracy). 

The data subjects have the right to file a petition for initiation of a personal data protection procedure with the supervisory authority, i.e. the Office for Personal Data Protection of the Slovak Republic, Park One Building, Námestie 1. mája 18, 811 06 Bratislava, tel.: +421 2 3231 3214, www.dataprotection.gov.sk.