Menu

How to Use the Null Coalescing Assignment Operator in C#

The null coalescing assignment operator (??=) introduced in C# 8.0 provides a cleaner way to assign a value to a variable only when it's null. Let's see how and when to use it effectively.

Quick Example

// Instead of writing this:
if (myVariable == null)
    myVariable = defaultValue;

// You can write this:
myVariable ??= defaultValue;

Real-World Examples

Simple Property Initialization

public class UserSettings
{
    private List<string> _preferences;
    
    public List<string> Preferences
    {
        get
        {
            _preferences ??= new List<string>();
            return _preferences;
        }
    }
}

Service Caching

public class ServiceCache
{
    private ApiClient _client;
    
    public ApiClient GetClient()
    {
        _client ??= new ApiClient("https://api.example.com");
        return _client;
    }
}

Lazy Configuration Loading

public class ConfigurationManager
{
    private Dictionary<string, string> _settings;
    
    public string GetSetting(string key)
    {
        _settings ??= LoadSettingsFromFile();
        return _settings.TryGetValue(key, out var value) ? value : null;
    }
    
    private Dictionary<string, string> LoadSettingsFromFile()
    {
        // Load settings logic here
        return new Dictionary<string, string>();
    }
}

Common Gotchas

Reference vs Value Types

The operator works differently with value types - they need to be nullable:

// This won't compile
int count ??= 1;

// This works
int? count ??= 1;

Chaining Operations

// You can chain the operator
string result = first ??= second ??= "default";

// Equivalent to:
if (first == null)
{
    if (second == null)
    {
        second = "default";
    }
    first = second;
}
result = first;

Thread Safety

The operator is not thread-safe by default:

// Not thread-safe
public class SharedCache
{
    private static Dictionary<string, object> _cache;
    
    public object GetItem(string key)
    {
        // Multiple threads could evaluate null simultaneously
        _cache ??= new Dictionary<string, object>();
        return _cache.GetValueOrDefault(key);
    }
}

// Thread-safe version
public class SharedCache
{
    private static Dictionary<string, object> _cache;
    private static readonly object _lock = new object();
    
    public object GetItem(string key)
    {
        lock (_lock)
        {
            _cache ??= new Dictionary<string, object>();
            return _cache.GetValueOrDefault(key);
        }
    }
}

Performance Considerations

The null coalescing assignment operator is compiled to efficient IL code. It generally performs the same as an explicit null check:

// These compile to similar IL
obj ??= new object();

if (obj == null)
    obj = new object();

When to Use It

✅ Good use cases:

  • Lazy initialization of properties
  • Caching values
  • Setting default values for nullable types
  • Simplifying null checks in property getters

❌ Avoid using when:

  • You need thread-safe initialization (use Lazy<T> instead)
  • The right-hand expression has side effects
  • You need more complex null-checking logic

Visual Studio Tips

You can use Quick Actions (Ctrl+.) to convert between traditional null checks and the ??= operator. Look for the suggestion "Use null coalescing assignment" when you have a pattern like:

if (variable == null)
    variable = value;

Version Compatibility

This feature requires:

  • C# 8.0 or later
  • .NET Core 3.0+ or .NET Standard 2.1+
  • Visual Studio 2019+
0
47

Related

Primary constructors, introduced in C# 12, offer a more concise way to define class parameters and initialize fields.

This feature reduces boilerplate code and makes classes more readable.

Traditional Approach vs Primary Constructor

Before primary constructors, you would likely write something like the following:

public class UserService
{
    private readonly ILogger _logger;
    private readonly IUserRepository _repository;

    public UserService(ILogger logger, IUserRepository repository)
    {
        _logger = logger;
        _repository = repository;
    }

    public async Task<User> GetUserById(int id)
    {
        _logger.LogInformation("Fetching user {Id}", id);
        return await _repository.GetByIdAsync(id);
    }
}

With primary constructors, this becomes:

public class UserService(ILogger logger, IUserRepository repository)
{
    public async Task<User> GetUserById(int id)
    {
        logger.LogInformation("Fetching user {Id}", id);
        return await repository.GetByIdAsync(id);
    }
}

Key Benefits

  1. Reduced Boilerplate: No need to declare private fields and write constructor assignments
  2. Parameters Available Throughout: Constructor parameters are accessible in all instance methods
  3. Immutability by Default: Parameters are effectively readonly without explicit declaration

Real-World Example

Here's a practical example using primary constructors with dependency injection:

public class OrderProcessor(
    IOrderRepository orderRepo,
    IPaymentService paymentService,
    ILogger<OrderProcessor> logger)
{
    public async Task<OrderResult> ProcessOrder(Order order)
    {
        try
        {
            logger.LogInformation("Processing order {OrderId}", order.Id);
            
            var paymentResult = await paymentService.ProcessPayment(order.Payment);
            if (!paymentResult.Success)
            {
                return new OrderResult(false, "Payment failed");
            }

            await orderRepo.SaveOrder(order);
            return new OrderResult(true, "Order processed successfully");
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Failed to process order {OrderId}", order.Id);
            throw;
        }
    }
}

Tips and Best Practices

  1. Use primary constructors when the class primarily needs dependencies for its methods
  2. Combine with records for immutable data types:
public record Customer(string Name, string Email)
{
    public string FormattedEmail => $"{Name} <{Email}>";
}
  1. Consider traditional constructors for complex initialization logic

Primary constructors provide a cleaner, more maintainable way to write C# classes, especially when working with dependency injection and simple data objects.

0
66

Raw string literals in C# provide a flexible way to work with multiline strings, with some interesting rules around how quotes work.

The key insight is that you can use any number of double quotes (three or more) to delimit your string, as long as the opening and closing sequences have the same number of quotes.

The Basic Rules

  1. You must use at least three double quotes (""") to start and end a raw string literal
  2. The opening and closing quotes must have the same count
  3. The closing quotes must be on their own line for proper indentation
  4. If your string content contains a sequence of double quotes, you need to use more quotes in your delimiter than the longest sequence in your content

Examples with Different Quote Counts

// Three quotes - most common usage
string basic = """
    This is a basic
    multiline string
    """;

// Four quotes - when your content has three quotes
string withThreeQuotes = """"
    Here's some text with """quoted""" content
    """";

// Five quotes - when your content has four quotes
string withFourQuotes = """""
    Here's text with """"nested"""" quotes
    """"";

// Six quotes - for even more complex scenarios
string withFiveQuotes = """"""
    Look at these """""nested""""" quotes!
    """""";

The N+1 Rule

The general rule is that if your string content contains N consecutive double quotes, you need to wrap the entire string with at least N+1 quotes. This ensures the compiler can properly distinguish between your content and the string's delimiters.

// Example demonstrating the N+1 rule
string example1 = """
    No quotes inside
    """; // 3 quotes is fine

string example2 = """"
    Contains """three quotes"""
    """"; // Needs 4 quotes (3+1)

string example3 = """""
    Has """"four quotes""""
    """""; // Needs 5 quotes (4+1)

Practical Tips

  • Start with three quotes (""") as your default
  • Only increase the quote count when you actually need to embed quote sequences in your content
  • The closing quotes must be on their own line and should line up with the indentation you want
  • Any whitespace to the left of the closing quotes defines the baseline indentation
// Indentation example
string properlyIndented = """
    {
        "property": "value",
        "nested": {
            "deeper": "content"
        }
    }
    """; // This line's position determines the indentation

This flexibility with quote counts makes raw string literals extremely versatile, especially when dealing with content that itself contains quotes, like JSON, XML, or other structured text formats.

0
68

Slow initial load times can drive users away from your React application. One powerful technique to improve performance is lazy loading - loading components only when they're needed.

Let's explore how to implement this in React.

The Problem with Eager Loading

By default, React bundles all your components together, forcing users to download everything upfront. This makes navigation much quicker and more streamlined once this initial download is complete.

However, depending on the size of your application, it could also create a long initial load time.

import HeavyComponent from './HeavyComponent';
import AnotherHeavyComponent from './AnotherHeavyComponent';

function App() {
  return (
    <div>
      {/* These components load even if user never sees them */}
      <HeavyComponent />
      <AnotherHeavyComponent />
    </div>
  );
}

React.lazy() to the Rescue

React.lazy() lets you defer loading components until they're actually needed:

import React, { lazy, Suspense } from 'react';

// Components are now loaded only when rendered
const HeavyComponent = lazy(() => import('./HeavyComponent'));
const AnotherHeavyComponent = lazy(() => import('./AnotherHeavyComponent'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <HeavyComponent />
        <AnotherHeavyComponent />
      </Suspense>
    </div>
  );
}

Route-Based Lazy Loading

Combine with React Router for even better performance:

import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

Implement these techniques in your React application today and watch your load times improve dramatically!

0
72