Modern C# Features Summary

Vaibhav • September 11, 2025

Over the course of Chapter 16, we’ve explored a wide range of advanced language features that define modern C#. These features are not just syntactic sugar - they represent a shift in how developers write expressive, safe, and high-performance code. From nullable reference types to caller information, each concept builds on the foundation laid in earlier chapters and introduces new capabilities that make C# more powerful and developer-friendly.

In this final article of the chapter, we’ll summarize everything we’ve learned, revisit key patterns, and reflect on how these features fit together. Whether you’re building APIs, desktop apps, games, or cloud services, mastering these tools will help you write cleaner, more maintainable, and more modern C# code.

Nullable Reference Types

We began with nullable reference types - a feature that helps prevent null reference exceptions by making nullability explicit. By annotating reference types with ?, you tell the compiler which variables may be null and which must not be. This enables flow analysis, warnings, and safer code.

You learned how to enable nullable context, annotate types, use null-forgiving operators, and apply defensive programming patterns. This feature encourages clarity and correctness - especially in large codebases and APIs.

Pattern Matching

Pattern matching lets you express complex logic using concise, readable syntax. You explored type patterns, constant patterns, relational patterns, logical patterns, property patterns, and positional patterns - all of which help you write cleaner conditionals and switch expressions.

Pattern matching is especially useful when working with data models, control flow, and validation. It replaces verbose if and switch chains with expressive, declarative logic.

Record Types

Records are immutable reference types designed for data modeling. They support value-based equality, concise syntax, and built-in features like with-expressions and deconstruction. You learned how to define positional records, customize properties, and use records in collections and APIs.

Records are ideal for DTOs, configuration objects, and any scenario where identity is based on content - not memory location.

Init-Only Properties

Init-only properties allow you to set values during object initialization but prevent changes afterward. This enforces immutability and improves safety - especially in models, configuration, and serialization.

You learned how to use init instead of set, combine it with object initializers and constructors, and apply it to records and classes alike.

Top-Level Statements

Top-level statements simplify your program’s entry point by removing boilerplate. You can write code directly - without a Main method or Program class. This is perfect for small apps, demos, scripts, and minimal APIs.

You explored how the compiler handles top-level code, how to declare methods and classes after top-level statements, and how to use them in real-world projects.

Target-Typed new

Target-typed new lets you omit the type on the right-hand side of an object creation when the type is clear from context. This reduces redundancy and improves readability.

You used it with collections, records, interfaces, fields, and properties - and saw how it fits into modern C# design.

Extension Methods

Extension methods let you add functionality to existing types without modifying them. You defined static methods with the this keyword, used them with strings, lists, and interfaces, and applied them to real-world scenarios like validation and LINQ.

Extension methods improve discoverability, readability, and modularity - especially in utility libraries and domain-specific APIs.

Partial Classes and Methods

Partial classes and methods allow you to split definitions across multiple files - useful for code generation, UI frameworks, and large classes. You learned how to define partial classes, implement optional partial methods, and use them to separate concerns and customize behavior.

This feature improves maintainability and collaboration - especially in designer-generated code and shared projects.

Operator Overloading

Operator overloading lets you define custom behavior for operators like +, ==, and <. You implemented arithmetic, comparison, and unary operators in your own types, and learned how to preserve immutability and consistency.

Operator overloading makes your types feel natural and intuitive - especially for mathematical and domain-specific models.

Indexers

Indexers let you use array-like syntax to access elements in your own types. You defined indexers with this, used them with integers and strings, and applied them to dictionaries, wrappers, and custom collections.

Indexers improve usability and encapsulation - making your types more expressive and user-friendly.

Attributes and Metadata

Attributes let you annotate your code with declarative metadata. You used built-in attributes like [Obsolete], [Serializable], and [TestMethod], and created custom attributes with AttributeUsage.

You also learned how to read attributes using reflection and apply them to classes, methods, properties, and parameters. Attributes are essential for diagnostics, tooling, frameworks, and interop.

Preprocessor Directives

Preprocessor directives control how your code is compiled. You used #define, #if, #region, and #pragma to manage build configurations, organize code, and suppress warnings.

These directives are useful for debugging, platform-specific code, and feature toggles - especially in large or multi-targeted projects.

Unsafe Code and Pointers

Unsafe code lets you work directly with memory using pointers. You declared unsafe blocks, used * and & operators, pinned memory with fixed, and allocated stack memory with stackalloc.

You also explored interop with native code and performance-critical scenarios. Unsafe code is powerful - but should be used sparingly and responsibly.

Dynamic Types

Dynamic types defer type checking until runtime. You declared dynamic variables, accessed members, handled runtime errors, and used ExpandoObject for flexible data structures.

Dynamic is useful for JSON, COM interop, scripting, and loosely typed data - but should be avoided in core logic and performance-sensitive code.

Caller Information

Caller Information lets you capture metadata about the calling code - including method name, file path, and line number. You used CallerMemberName, CallerFilePath, and CallerLineNumber to simplify logging, diagnostics, and property change notifications.

This feature improves transparency and maintainability - especially in utility methods and frameworks.

String Interpolation Advanced

String interpolation lets you embed expressions directly into strings using the $ symbol. You formatted numbers and dates, aligned text, escaped braces, used expressions, and combined interpolation with logging, culture, and performance tools.

You also explored raw string literals and FormattableString for advanced scenarios. Interpolation makes your output cleaner, more expressive, and easier to maintain.

Local Functions Advanced

Local functions are methods declared inside other methods. You used them to encapsulate helper logic, access outer variables, handle recursion, validate input, and structure async workflows.

Local functions improve readability, reduce scope pollution, and offer better performance than lambdas - especially in structured logic and utility methods.

Putting It All Together

Modern C# features are designed to make your code safer, cleaner, and more expressive. They build on the principles of object-oriented programming, functional design, and performance optimization - while staying true to C#’s roots as a statically typed, managed language.

As you continue your journey, remember that these features are tools - not rules. Use them thoughtfully, combine them with good design practices, and always prioritize clarity and maintainability. Whether you’re writing a small utility or a large enterprise system, modern C# gives you the power to build robust, elegant solutions.

Summary

Chapter 16 introduced you to the advanced features that define modern C#. You explored nullable reference types, pattern matching, records, init-only properties, top-level statements, target-typed new, extension methods, partial classes and methods, operator overloading, indexers, attributes, preprocessor directives, unsafe code, dynamic types, caller information, string interpolation, and local functions.

Each feature adds depth and flexibility to your C# toolkit. Together, they empower you to write expressive, safe, and high-performance code. With this foundation, you’re ready to tackle real-world projects and explore even more advanced topics in the chapters ahead.