Advanced Features Overview
Reflection
Reflection allows a program to inspect and manipulate its own type information at runtime. The Type class is the core of reflection, providing access to metadata such as fields, properties, and methods.
Getting Type Information
using System;
using System.Reflection;
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public void SayHello() => Console.WriteLine("Hello!");
}
class Program
{
static void Main()
{
Type type1 = typeof(Person);
Type type2 = new Person().GetType();
Console.WriteLine($"Type name: {type1.Name}");
Console.WriteLine("Properties:");
foreach (PropertyInfo p in type1.GetProperties())
Console.WriteLine($" {p.Name} ({p.PropertyType.Name})");
Console.WriteLine("Methods:");
foreach (MethodInfo m in type1.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
Console.WriteLine($" {m.Name}");
}
}
Type name: Person
Properties:
Name (String)
Age (Int32)
Methods:
get_Name
set_Name
get_Age
set_Age
SayHello
Creating Instances Dynamically
using System;
using System.Reflection;
class Dog
{
public string Breed { get; set; } = "Unknown";
public override string ToString() => $"Dog: {Breed}";
}
class Program
{
static void Main()
{
Type type = typeof(Dog);
object obj = Activator.CreateInstance(type)!;
PropertyInfo prop = type.GetProperty("Breed")!;
prop.SetValue(obj, "Husky");
Console.WriteLine(obj);
}
}
Dog: Husky
Example
using System;
using System.Linq;
using System.Reflection;
class Calculator
{
public int Add(int a, int b) => a + b;
public double Multiply(double x, double y) => x * y;
public void Reset() { }
}
class Program
{
static void Main()
{
Calculator calc = new Calculator();
Type type = calc.GetType();
Console.WriteLine($"Type: {type.Name}");
Console.WriteLine($"Namespace: {type.Namespace}");
Console.WriteLine($"Is class: {type.IsClass}");
Console.WriteLine("Public methods:");
MethodInfo[] methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
foreach (MethodInfo m in methods)
{
string parameters = string.Join(", ", m.GetParameters().Select(p => $"{p.ParameterType.Name} {p.Name}"));
Console.WriteLine($" {m.ReturnType.Name} {m.Name}({parameters})");
}
}
}
Type: Calculator
Namespace:
Is class: True
Public methods:
Int32 Add(Int32 a, Int32 b)
Double Multiply(Double x, Double y)
Void Reset()
Attributes
Attributes are declarative tags used to add metadata to code elements. You can use built-in attributes or define custom ones.
Built-in Attributes
using System;
class LegacyService
{
[Obsolete("Please use NewService instead", error: false)]
public void OldMethod() => Console.WriteLine("Old method");
public void NewMethod() => Console.WriteLine("New method");
}
class Program
{
static void Main()
{
var service = new LegacyService();
service.OldMethod();
service.NewMethod();
}
}
Old method
New method
error parameter of Obsolete is set to true, calling the old method will produce a compile error instead of a warning.
Custom Attributes
using System;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
class DescriptionAttribute : Attribute
{
public string Text { get; }
public DescriptionAttribute(string text) => Text = text;
}
[Description("User management service")]
class UserService
{
[Description("Get all users")]
public void GetAll() { }
}
class Program
{
static void Main()
{
var attr = Attribute.GetCustomAttribute(typeof(UserService), typeof(DescriptionAttribute)) as DescriptionAttribute;
Console.WriteLine(attr?.Text ?? "No description");
}
}
User management service
Generic Constraints In Depth
Generic constraints use the where keyword to restrict the range of type parameters, ensuring they meet specific conditions.
Constraint Types Overview
| Constraint | Description |
|---|---|
where T : class |
T must be a reference type |
where T : struct |
T must be a value type (cannot be null) |
where T : new() |
T must have a parameterless public constructor |
where T : ISpecific |
T must implement the specified interface |
where T : BaseClass |
T must inherit from the specified base class |
where T : unmanaged |
T must be an unmanaged type |
where T : notnull |
T must be a non-nullable type |
Example
using System;
interface IRepository
{
string GetName();
}
class SqlRepository : IRepository
{
public string GetName() => "SQL Repository";
}
class Service<T> where T : class, IRepository, new()
{
private T _repo = new T();
public void Print() => Console.WriteLine(_repo.GetName());
}
class Program
{
static void Main()
{
var service = new Service<SqlRepository>();
service.Print();
}
}
SQL Repository
new() constraint must appear last in the constraint list; class or struct should come first.
Iterators (yield)
Iterators use yield return to produce sequence elements one at a time, implementing lazy evaluation — elements are computed only when iterated.
yield return and yield break
using System;
using System.Collections.Generic;
class Program
{
static IEnumerable<int> GetNumbers()
{
for (int i = 0; i < 5; i++)
{
if (i == 3) yield break;
yield return i;
}
}
static void Main()
{
foreach (int n in GetNumbers())
Console.WriteLine(n);
}
}
0
1
2
yield break immediately terminates iteration. Iterator methods return IEnumerable<T> or IEnumerator<T>.
Example
using System;
using System.Collections.Generic;
class Program
{
static IEnumerable<int> Fibonacci(int count)
{
int a = 0;
int b = 1;
for (int i = 0; i < count; i++)
{
yield return a;
int temp = a + b;
a = b;
b = temp;
}
}
static void Main()
{
Console.WriteLine("Fibonacci sequence, first 10 terms:");
foreach (int n in Fibonacci(10))
{
Console.Write(n + " ");
}
Console.WriteLine();
}
}
Fibonacci sequence, first 10 terms:
0 1 1 2 3 5 8 13 21 34
Span<T> and Memory<T>
Span<T> and Memory<T> provide copy-free sliced access to contiguous memory regions, making them key types for high-performance scenarios.
Span<T> Basic Usage
using System;
class Program
{
static void Main()
{
int[] array = { 10, 20, 30, 40, 50 };
Span<int> span = array.AsSpan();
Span<int> slice = span.Slice(1, 3);
slice[0] = 99;
Console.WriteLine(string.Join(", ", array));
Span<int> stackSpan = stackalloc int[3];
stackSpan[0] = 1;
stackSpan[1] = 2;
stackSpan[2] = 3;
Console.WriteLine(string.Join(", ", stackSpan.ToArray()));
}
}
10, 99, 30, 40, 50
1, 2, 3
Span<T> can only be stored on the stack; it cannot be used as a class field or across an await boundary. Use Memory<T> when you need to span across stack frames.
Regular Expressions (Regex)
The System.Text.RegularExpressions.Regex class is used for pattern matching, searching, and replacing text.
Common Operations
using System;
using System.Text.RegularExpressions;
class Program
{
static void Main()
{
string input = "Contact phone: 138-1234-5678, Email: test@example.com";
bool hasPhone = Regex.IsMatch(input, @"\d{3}-\d{4}-\d{4}");
Console.WriteLine($"Contains phone: {hasPhone}");
Match phoneMatch = Regex.Match(input, @"\d{3}-\d{4}-\d{4}");
Console.WriteLine($"Phone: {phoneMatch.Value}");
MatchCollection emails = Regex.Matches(input, @"[\w.]+@[\w.]+");
foreach (Match m in emails)
Console.WriteLine($"Email: {m.Value}");
string replaced = Regex.Replace(input, @"\d{4}", "****");
Console.WriteLine($"Replaced: {replaced}");
}
}
Contains phone: True
Phone: 138-1234-5678
Email: test@example.com
Replaced: Contact phone: 138-**-**, Email: test@example.com
Common Patterns Quick Reference
| Pattern | Description |
|---|---|
\d |
Digit |
\w |
Letter/digit/underscore |
\s |
Whitespace |
. |
Any character (except newline) |
^ / $ |
Start of line / End of line |
{n,m} |
Repeat n to m times |
[abc] |
Character set |
Unsafe Code
The unsafe context allows the use of pointers and manual memory operations, applicable for interop or extreme performance scenarios.
Pointers and fixed
using System;
class Program
{
static unsafe void Main()
{
int value = 42;
int* ptr = &value;
Console.WriteLine($"Value: {*ptr}");
Console.WriteLine($"Address: {(long)ptr}");
int[] arr = { 1, 2, 3 };
fixed (int* p = arr)
{
Console.WriteLine($"First element: {*p}");
Console.WriteLine($"Second element: {*(p + 1)}");
}
}
}
Value: 42
Address: 12345678
First element: 1
Second element: 2
unsafe requires enabling <AllowUnsafeBlocks>true</AllowUnsafeBlocks> in the project file. The fixed statement pins an object to prevent the GC from moving the memory.
C# Version Features Overview (7–12)
| Version | Key Features | Brief Description |
|---|---|---|
| C# 7 | out variables, tuples, pattern matching | out int x inline declaration, (int, string) tuples, is Type pattern |
| C# 7 | local functions, ref returns | Nested functions within methods, reference return values |
| C# 8 | nullable reference types, async streams | string? nullable annotation, IAsyncEnumerable<T> |
| C# 8 | default interface methods, indices and ranges | Default interface implementations, [^1], [1..3] |
| C# 9 | record types, init setters | record immutable types, init init-only properties |
| C# 9 | top-level statements, target-typed new | No Main boilerplate, new() type omission |
| C# 10 | global using, file-scoped namespaces | global using X;, namespace Ns; single line |
| C# 10 | record structs, const interpolated strings | record struct, constant interpolated strings |
| C# 11 | raw string literals, required | """...""" multi-line raw strings, required forced initialization |
| C# 11 | list patterns, UTF8 literals | [1, 2, ..] matching, "hello"u8 |
| C# 12 | primary constructors | class C(int x) class declaration as constructor |
| C# 12 | collection expressions, alias any type | [1, 2, 3] uniform initialization, using P = int* |
❓ FAQ
📖 Summary
- Reflection accesses type metadata at runtime through the Type class and can dynamically create instances
- Attributes add declarative metadata to code, supporting custom definitions and runtime reading
- Generic constraints (where) restrict type parameters to reference types, value types, types with constructors, etc.
- yield return implements lazy iteration; yield break terminates enumeration
- Span<T> provides stack-based zero-copy slicing; Memory<T> can cross async boundaries
- The Regex class supports pattern matching, searching, replacing, and other text operations
- The unsafe context allows pointer operations, requiring fixed to pin memory
- C# 7–12 continuously introduced tuples, nullable references, record types, primary constructors, and more
📝 Exercises
- Use reflection to get all public method names of the
stringclass and output them - Create a custom
[Author]attribute, apply it to a class, and read it via reflection - Write a generic method
T Create<T>() where T : new()that creates and returns an instance - Use yield return to implement an iterator that generates the first 20 terms of the Fibonacci sequence
- Use Regex to validate whether a string is a valid IPv4 address format



