Back to Blog
C#

C# where T notnull: Generic Constraint Explained

c# where t notnull: Understand the C# notnull generic constraint, how it works with nullable reference types, and when to apply it in your generic APIs.

C# genericstype constraintsnullable reference typesgeneric API designcompile-time safety
Diagram showing a generic type parameter T with a notnull constraint, excluding nullable types.

c# where t notnull requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.

The where T : notnull constraint in C# tells the compiler that a generic type parameter T must never be null. It is part of the nullable reference type feature introduced in C# 8 and is enforced at compile time. This constraint is particularly useful when you design generic classes or methods that need to guarantee non-null inputs or outputs, especially when callers might pass reference types or nullable value types.

What the notnull Constraint Does

notnull is a generic type constraint that restricts the type argument to non-nullable types. For reference types, it means the type must be a non-nullable reference type (e.g., string rather than string?). For value types, it means the type must be a non-nullable value type (e.g., int rather than int?). The constraint is enforced at compile time; the runtime does not perform additional checks because the nullability information is erased after compilation.

Consider this generic method:

public static T GetDefault<T>() where T : notnull { return default; }

For T = string, default is null, which violates the constraint. The compiler will warn if you try to call GetDefault<string>() because string is a non-nullable reference type, but default produces null. In practice, you would implement the method body to return a non-null value, but the constraint itself does not change runtime behavior—it only enforces that the type argument is non-nullable.

Syntax and Basic Usage

The notnull constraint appears in the where clause of a generic type or method declaration. It can be combined with other constraints, but it cannot be used with class or struct because those already imply a category of type. The syntax is straightforward:

public class Repository<T> where T : notnull { public void Save(T entity) { /* ... */ } }

Here, T can be string, int, Customer, or any other non-nullable type. It cannot be string?, int?, or Customer? if nullable reference types are enabled. If a caller tries to use a nullable type, the compiler emits an error.

You can also use notnull with multiple constraints:

public TValue GetValue<TKey, TValue>(TKey key) where TKey : notnull, IComparable<TKey> { // ... }

This requires TKey to be non-nullable and implement IComparable<TKey>. The order of constraints matters: notnull must appear first if it is combined with other constraints.

notnull vs class vs struct

The class constraint restricts T to reference types, and struct restricts it to value types. The notnull constraint is broader: it accepts both reference and value types, as long as they are non-nullable. The key difference is that class and struct are about the category of the type, while notnull is about nullability.

ConstraintAllows reference typesAllows value typesAllows nullable types
classYesNoNo (with nullable enabled)
structNoYesNo (because nullable value types are Nullable<T>)
notnullYes (non-nullable)Yes (non-nullable)No

In practice, notnull is useful when you want to accept both string and int but reject string? and int?. The class constraint would reject int, and struct would reject string. notnull gives you flexibility while still preventing null arguments.

Interaction with Nullable Reference Types

The notnull constraint is meaningful only when nullable reference types are enabled in your project. If you are not using nullable reference types, the constraint has no effect because all reference types are considered non-nullable by default. In a project with <Nullable>enable</Nullable>, the compiler treats string as non-nullable and string? as nullable. The notnull constraint ensures that the generic type argument is not a nullable reference type.

This becomes important when you have a generic method that should not accept null, but you also want to support both reference and value types. Without notnull, you might be tempted to use where T : class to reject value types, but that would be too restrictive. The notnull constraint allows the method to accept string and int while rejecting string? and int?.

Consider this example:

public static void PrintName<T>(T value) where T : notnull { Console.WriteLine(value.ToString()); }

Calling `PrintName<string>(

c# where t notnull: Practical Usage and Code Examples | RYUSLOG DEV