Table of Contents

C# Style Guide for 2sxc / EAV

Important: If you only want to USE 2sxc / EAV, then you do NOT need this. This is meant for people who want to contribute to the source code of 2sxc and EAV.

Concept

We want a consistent C# style across all 2sxc and EAV code, so that it is easy to read and maintain. This is why we have a C# Style Guide, which is based on the Microsoft C# Style Guide.

Tip

We highly recommend using ReSharper or Rider. They will recommend all of the style rules in this document, and it will also help you to automatically apply them.

In many cases you can use it for free on open source projects, or you can use the free Rider Community Edition.

Style Guide

  • Use PascalCase for class names, method names, and property names.
  • Use camelCase for local variables and method parameters.
  • Use meaningful names for classes, methods, and properties.
  • Use var when the type is obvious from the right-hand side of the assignment - even for out parameters.
    • Do use explicit types when the type is not obvious or when it improves readability.
  • Clean up after yourself: remove unused namespaces, variables, methods, and classes.
  • Use global using for common global namespaces from core libraries such as ToSic.Sys.DI.
  • Use async and await for asynchronous programming.

2. Use Latest (Preview) C# Features

We want to use the latest C# features, so we can use the latest language features and improvements. This means we will use the latest C# version available in the .NET SDK we are using. By default, this is already enabled in all .csproj files using <LangVersion>preview</LangVersion>.

Important features to use are:

2.1 Use File Scoped Namespaces

File Scoped Namespaces

namespace ToSic.Sxc.Example;

public class ExampleClass
{
  public void ExampleMethod()
  {
    // Method implementation
  }
}

2.2 Use field Keyword and Init Setters

namespace ToSic.Sxc.Example;
public class ExampleClass
{
  public string InitOnly { get; init; }

  public string ExampleOne { get => field ??= "default value"; set; }

  public string ExampleProperty 
  {
      get => field ?? "fallback value";
      set;
  } = "default value";
}

2.4 Use [] for Collection Initializers

namespace ToSic.Sxc.Example;
public class ExampleClass
{
  public List<string> ExampleList { get; set; } = [];

  public Dictionary<string, string> ExampleDictionary { get; set; } = [];

  public List<string> ExampleListWithItems { get; set; } = ["item1", "item2"];

  // Using C# 12 Collection Expressions to Copy Lists
  public List<string> ExampleListWithExpression { get;  } = [..ExampleListWithItems, "item3", "item4"];
}

2.5 Use => for Simple Methods and Properties

namespace ToSic.Sxc.Example;
public class ExampleClass
{
  public string ExampleMethod(string input) => $"Hello, {input}!";

  public string ExampleProperty => $"Hello, {input}!";
}

3.2 Prefer Ternary Operators

namespace ToSic.Sxc.Example;
public class ExampleClass
{
  public string ExampleMethod(string input)
  {
    return string.IsNullOrEmpty(input)
      ? "Input is empty"
      : $"Hello, {input}!";
  }
}

3.3 Use switch Expressions

namespace ToSic.Sxc.Example;
public class ExampleClass
{
  public string ExampleMethod(string input)
  {
    return input switch
    {
      null => "Input is null",
      "" => "Input is empty",
      _ => $"Hello, {input}!"
    };
  }
}

3.4 Use Ternary Throws

namespace ToSic.Sxc.Example;
public class ExampleClass
{
  public string ExampleMethod(string input)
  {
    return input ?? throw new ArgumentException("Input cannot be null or empty", nameof(input))
  }

  public string ExampleMethodWithEmptyCheck(string input)
  {
    return string.IsNullOrEmpty(input) ? throw new ArgumentException("Input cannot be null or empty", nameof(input)) : input;
  }
}

4. Use LINQ properly

LINQ is easy to learn and hard to master. Here are some tips to use LINQ properly:

  • Use Select and Where to filter and transform collections.
  • Use FirstOrDefault and SingleOrDefault to get single items from collections.
  • Use Any and All to check conditions on collections.
  • Use Aggregate to combine collections into a single value.
  • Use ToList and ToArray to convert collections to lists or arrays.
  • Use AsEnumerable to convert collections to IEnumerable<T> before using LINQ methods.
  • Use IEnumerable<T> instead of List<T> or Array when possible, to allow for more flexibility.
  • Use IReadOnlyList<T>, IReadOnlyCollection<T> or IImmutableList<T> for read-only collections.
Tip

It's usually better to use a ToList() or ToArray() at the end of a LINQ query, so that it is clear that the query is executed at that point. This also helps to avoid performance issues with deferred execution, where the query is executed multiple times.

It also helps debug issues where they occur, and not later when the query is executed.

5. Use IServiceProvider for Dependency Injection

We use IServiceProvider for dependency injection, and register services in the Startup.cs file. This allows us to easily inject dependencies into classes and methods, and makes the code more testable and maintainable.

6. Use Proper Nullability Annotations

We use proper nullability annotations to indicate whether a parameter or return value can be null or not. This helps to avoid null reference exceptions and makes the code more readable.

Tip

As of v20, all the core libraries are now using nullable reference types. This include ToSic.Sys, ToSic.Sxc, and ToSic.Eav.

The Dnn / Oqtane libraries are not fully nullable yet, but will be in the future.

History

  • New in 2023-09 v16.06

Shortlink: https://go.2sxc.org/build