Anonymous & Nullable Types
Anonymous Types
Definition
Anonymous types are unnamed types created inline using the new keyword with object initializer syntax. The compiler generates a class with read-only properties and infers their types from the assigned values. They are commonly used in LINQ projections and short-lived data shaping.
Creating Anonymous Types
// The compiler creates a class with Name (string) and Age (int) properties
var person = new { Name = "John", Age = 32 };
// Access properties like any other object
Console.WriteLine($"{person.Name}, {person.Age}"); // John, 32
The var Keyword
Anonymous types have no known name at compile time, so you must use var to hold them. The var keyword tells the compiler to infer the type from the initialization expression:
var number = 15; // int
var word = "example"; // string
var money = 987.32; // double
Key Characteristics
- Read-only properties — values cannot be changed after creation
- Compiler-generated — the compiler creates a hidden class with
Equals,GetHashCode, andToString - Type inference — property types are inferred from values
- Scope-limited — primarily used within a method; cannot be passed as return types (use tuples or records instead)
var p1 = new { Name = "Alice", Age = 30 };
var p2 = new { Name = "Alice", Age = 30 };
// Two anonymous objects with the same property names, types, and values are equal
Console.WriteLine(p1.Equals(p2)); // True
// But they are not the same reference
Console.WriteLine(ReferenceEquals(p1, p2)); // False
Common Use: LINQ Projections
var results = products
.Select(p => new { p.Name, p.Price })
.Where(x => x.Price > 100);
foreach (var item in results)
{
Console.WriteLine($"{item.Name}: ${item.Price}");
}
Anonymous types cannot cross method boundaries as return types. If you need to return a shaped result from a method, use tuples (string Name, decimal Price) or record types instead.
Nullable Types
Definition
Nullable types represent values that may also be null. C# supports two kinds: nullable value types (int?, bool?) which wrap System.Nullable<T>, and nullable reference types (C# 8+), a compile-time feature that annotates whether a reference type can be null. Together they form C#'s approach to null safety.
Nullable Value Types
A nullable value type T? can hold all values of its underlying type T plus null. The compiler translates T? into System.Nullable<T>:
int? age = null;
int? score = 85;
// Behind the scenes: Nullable<int>
Console.WriteLine(age.HasValue); // False
Console.WriteLine(score.HasValue); // True
Console.WriteLine(score.Value); // 85
HasValue and Value Properties
int? count = GetCount();
if (count.HasValue)
{
int value = count.Value; // Access the underlying int
Console.WriteLine($"Count: {value}");
}
// Accessing .Value when HasValue is false throws InvalidOperationException
Nullable to Non-Nullable Conversion
int? maybe = 42;
// .Value — throws if null
int a = maybe.Value;
// .GetValueOrDefault() — returns default (0) if null
int b = maybe.GetValueOrDefault();
// .GetValueOrDefault(fallback) — returns fallback if null
int c = maybe.GetValueOrDefault(-1);
// Null-coalescing operator — returns right side if left is null
int d = maybe ?? 0;
// GetValueOrDefault() is the safest approach when you want a default
Nullable Arithmetic
Any arithmetic or comparison operation involving null produces null:
int? x = 10, y = null;
int? sum = x + y; // null
int? product = x * y; // null
bool? equal = x == y; // null (three-valued logic)
// Comparisons with null are false (except !=)
Console.WriteLine(x > y); // false
Console.WriteLine(x == y); // false
Console.WriteLine(x != y); // true
Nullable Reference Types (C# 8+)
Nullable reference types are a compile-time analysis feature — not a runtime change. When enabled, the compiler warns when a reference that could be null is dereferenced:
#nullable enable
string name = "Alice"; // Non-nullable — cannot be null
string? nickname = null; // Nullable — can be null
// Compiler warning CS8602: possible null reference
Console.WriteLine(nickname.Length);
// Safe access with null-conditional
Console.WriteLine(nickname?.Length); // null (no warning)
Console.WriteLine(nickname?.Length ?? 0); // 0
How Nullable Reference Types Work
The compiler performs null state analysis on every reference variable:
| State | Meaning |
|---|---|
| Not null | Definitely assigned a non-null value |
| Maybe null | Could be null at this point |
#nullable enable
void Process(string? input)
{
// input is "maybe null" here
if (input is null)
return;
// input is "not null" here — compiler knows
Console.WriteLine(input.Length); // No warning
}
Null-Related Operators
string? name = GetName();
// ?. — null-conditional operator: returns null if operand is null
int? length = name?.Length;
// ?? — null-coalescing operator: returns right side if left is null
int safeLength = name?.Length ?? 0;
// ??= — null-coalescing assignment: assigns only if left is null
name ??= "Unknown";
// ! — null-forgiving operator: tells compiler "I know this isn't null"
// Does NOT do a runtime check — only suppresses the warning
int forceLength = name!.Length;
The null-forgiving operator (!) only suppresses compiler warnings. It does not add a runtime null check. If the value is actually null, you will still get a NullReferenceException at runtime.
Enabling Nullable Reference Types
<!-- Project level — .csproj -->
<PropertyGroup>
<Nullable>enable</Nullable>
</PropertyGroup>
// File level
#nullable enable
// Disable for a section
#nullable disable
string legacy = null; // No warning
#nullable enable
// Restore to project setting
#nullable restore
Nullable Attributes
The compiler uses attributes to understand null state across method boundaries:
// Return value is not null when the method returns true
bool TryGetValue([NotNullWhen(true)] out string? value);
// Parameter may be null when the method returns false
[return: MaybeNullWhen(false)]
T Find<T>(Predicate<T> match);
// Parameter is not null after the method returns
void EnsureCapacity([NotNull] ref string? value);
// Parameter will not be null if the return value is not null
[return: NotNullIfNotNull(nameof(input))]
string? Transform(string? input);
| Attribute | Meaning |
|---|---|
[NotNullWhen(true)] | Parameter is not null when method returns true |
[NotNullWhen(false)] | Parameter is not null when method returns false |
[MaybeNullWhen(true)] | Parameter may be null when method returns true |
[NotNull] | Output is guaranteed not null |
[NotNullIfNotNull] | Return is not null if the specified parameter is not null |
[MemberNotNullWhen] | Named member is not null when method returns the value |
When to Use
- Database values — map nullable columns to nullable types (
int?,DateTime?) - API boundaries — nullable reference types enforce null contracts at compile time
- Optional parameters — use
T?whennullmeans "not provided" - Legacy migration — enable NRTs incrementally with
#nullable enableper file
Common Pitfalls
Do not use ! or #nullable disable to silence warnings without understanding the null state. Each suppressed warning is a potential NullReferenceException at runtime. Fix the root cause: add null checks, provide defaults, or change the type.
If your public API accepts string but your implementation can produce null, callers will not expect null. Make the signature honest: use string? if null is possible. Nullable reference types make these contracts explicit.
For large codebases, enable nullable reference types one file or project at a time. Use #nullable enable at the top of files you want to migrate and fix warnings before expanding to more files.
Key Takeaways
- Anonymous types are compiler-generated, read-only classes created inline with
new { ... }. - Use
varto hold anonymous type instances since the type name is unknown at compile time. T?on value types createsNullable<T>with.HasValueand.Value.T?on reference types is a compile-time annotation — no runtime change.- The null-coalescing operator
??and null-conditional operator?.are the primary tools for null-safe code. - Nullable reference types with
#nullable enablecatch null bugs at compile time. - The
!operator suppresses warnings but does not add runtime checks — use sparingly. - Nullable attributes (
[NotNullWhen], etc.) help the compiler understand method contracts.
Interview Questions
Q: What is an anonymous type in C#?
An anonymous type is a compiler-generated class with no explicit name, created using new { Property = value } syntax. Properties are read-only and their types are inferred from the assigned values. You must use var to hold instances. They are commonly used in LINQ projections for shaping data.
Q: Can you return an anonymous type from a method?
No, anonymous types cannot be used as method return types because their type name is compiler-generated and inaccessible in code. Use tuples (string Name, int Age) or record types instead when you need to return shaped data from a method.
Q: What is a nullable value type?
A nullable value type T? is a System.Nullable<T> struct that can hold all values of T plus null. It exposes .HasValue (bool) and .Value (T) properties. It is useful for representing optional or database values.
Q: What are nullable reference types?
Nullable reference types (C# 8+) are a compile-time analysis feature. When enabled, string? means "this reference can be null" and string means "this reference should not be null." The compiler warns when a nullable reference is dereferenced without a null check. This is not a runtime change — it only produces warnings.
Q: What is the difference between int and int??
int is a value type stored on the stack that cannot be null. int? is Nullable<int>, a struct that wraps an int with a HasValue flag, allowing it to represent null. int? has a larger memory footprint and requires .Value or .GetValueOrDefault() to access the underlying value.
Q: What does the ?. operator do?
The null-conditional operator (?.) short-circuits member access if the operand is null. If obj is null, obj?.Property returns null instead of throwing NullReferenceException. The return type is always nullable. Chain it with ?? to provide a default: obj?.Property ?? fallback.
Q: What is the difference between nullable value types and nullable reference types?
Nullable value types (int?) are a runtime feature — they wrap the value in Nullable<T> with extra state. Nullable reference types (string?) are a compile-time-only feature — the compiler analyzes null states and produces warnings, but the runtime representation is the same as a normal reference.