C# & ASP.NET Topic
C# Advanced Features
1. Records
What are Records?
Records are immutable reference types that provide value-based equality semantics. Perfect for data carriers where identity is based on content.
Detailed Examples:
Basic Record:
csharp
// Simple record - immutable by default
public record Person(string FirstName, string LastName, int Age);
// Equivalent traditional class would be much more code!
public class TraditionalPerson
{
public string FirstName { get; init; }
public string LastName { get; init; }
public int Age { get; init; }
public TraditionalPerson(string firstName, string lastName, int age)
{
FirstName = firstName;
LastName = lastName;
Age = age;
}
// Would need to override Equals, GetHashCode, ToString, etc.
}
// Usage
public class RecordDemo
{
public static void DemonstrateBasicRecords()
{
// Creation with positional parameters
var person1 = new Person("John", "Doe", 30);
var person2 = new Person("John", "Doe", 30);
var person3 = new Person("Jane", "Smith", 25);
// Value-based equality
Console.WriteLine($"person1 == person2: {person1 == person2}"); // True
Console.WriteLine($"person1 == person3: {person1 == person3}"); // False
// Built-in ToString()
Console.WriteLine(person1); // Person { FirstName = John, LastName = Doe, Age = 30 }
// Deconstruction
var (firstName, lastName, age) = person1;
Console.WriteLine($"First: {firstName}, Last: {lastName}, Age: {age}");
// With-expressions for non-destructive mutation
var olderPerson = person1 with { Age = 31 };
Console.WriteLine(olderPerson); // Age is now 31
}
}
Advanced Record Features:
csharp
// Record with additional members
public record Employee(string FirstName, string LastName, int Age)
{
// Additional properties
public string FullName => $"{FirstName} {LastName}";
public string Email => $"{FirstName.ToLower()}.{LastName.ToLower()}@company.com";
// Methods
public bool IsSenior() => Age >= 40;
// Validation in constructor
public Employee : this(FirstName, LastName, Age)
{
if (Age < 18)
throw new ArgumentException("Employee must be at least 18 years old");
}
}
// Record structs (C# 10+)
public readonly record struct Point(int X, int Y)
{
public double DistanceFromOrigin => Math.Sqrt(X * X + Y * Y);
public static Point operator +(Point a, Point b) =>
new Point(a.X + b.X, a.Y + b.Y);
}
// Inheritance with records
public abstract record Shape(string Color);
public record Circle(string Color, double Radius) : Shape(Color);
public record Rectangle(string Color, double Width, double Height) : Shape(Color);
public class AdvancedRecordDemo
{
public static void DemonstrateAdvancedRecords()
{
// Record with additional members
var emp = new Employee("Alice", "Johnson", 28);
Console.WriteLine(emp.FullName); // Alice Johnson
Console.WriteLine(emp.Email); // alice.johnson@company.com
Console.WriteLine(emp.IsSenior()); // False
// Record struct
var point1 = new Point(3, 4);
var point2 = new Point(1, 2);
var point3 = point1 + point2;
Console.WriteLine($"Point: {point3}, Distance: {point3.DistanceFromOrigin}");
// Inheritance
Shape circle = new Circle("Red", 5.0);
Shape rectangle = new Rectangle("Blue", 10.0, 5.0);
Console.WriteLine(circle); // Circle { Color = Red, Radius = 5 }
// Pattern matching with records (we'll cover this next)
if (circle is Circle { Radius: > 3.0 } largeCircle)
{
Console.WriteLine($"Large circle with radius: {largeCircle.Radius}");
}
}
}
2. Pattern Matching
Pattern matching provides a concise way to test values and extract information.
Detailed Examples:
Basic Pattern Matching:
csharp
public class PatternMatchingDemo
{
public static void DemonstrateBasicPatterns()
{
object[] data = { 42, "Hello", 3.14, null, "World", 100, new int[] { 1, 2, 3 } };
foreach (var item in data)
{
// Type patterns
string result = item switch
{
// Constant pattern
null => "Null value",
// Type pattern with condition
int i when i > 50 => $"Large integer: {i}",
int i => $"Integer: {i}",
// Type pattern
string s when s.Length > 5 => $"Long string: {s}",
string s => $"String: {s}",
// Property pattern (C# 8+)
double d => $"Double: {d}",
// Discard pattern
_ => "Unknown type"
};
Console.WriteLine(result);
}
}
public static void PropertyPatterns()
{
var people = new Person[]
{
new Person("John", "Doe", 25),
new Person("Jane", "Smith", 35),
new Person("Bob", "Johnson", 45),
new Person("Alice", "Williams", 28)
};
foreach (var person in people)
{
var category = person switch
{
{ Age: < 30 } => "Young",
{ Age: >= 30 and < 40 } => "Middle-aged",
{ Age: >= 40 } => "Senior",
_ => "Unknown"
};
Console.WriteLine($"{person.FirstName} is {category}");
}
}
public static void TuplePatterns()
{
var gameStates = new[] { ("rock", "scissors"), ("paper", "rock"), ("scissors", "paper"), ("rock", "rock") };
foreach (var (player1, player2) in gameStates)
{
var result = (player1, player2) switch
{
("rock", "scissors") => "Player 1 wins",
("paper", "rock") => "Player 1 wins",
("scissors", "paper") => "Player 1 wins",
("scissors", "rock") => "Player 2 wins",
("rock", "paper") => "Player 2 wins",
("paper", "scissors") => "Player 2 wins",
(_, _) when player1 == player2 => "Tie",
_ => "Invalid move"
};
Console.WriteLine($"{player1} vs {player2}: {result}");
}
}
}
Advanced Pattern Matching:
csharp
public abstract record Shape(string Color);
public record Circle(string Color, double Radius) : Shape(Color);
public record Rectangle(string Color, double Width, double Height) : Shape(Color);
public record Triangle(string Color, double Base, double Height) : Shape(Color);
public class AdvancedPatternMatching
{
public static double CalculateArea(Shape shape)
{
return shape switch
{
// Property pattern with nested patterns
Circle { Radius: var r } => Math.PI * r * r,
Rectangle { Width: var w, Height: var h } => w * h,
Triangle { Base: var b, Height: var h } => 0.5 * b * h,
// Pattern with when clause
Circle { Radius: > 10 } largeCircle =>
Math.PI * largeCircle.Radius * largeCircle.Radius,
// Recursive pattern
Circle c => Math.PI * c.Radius * c.Radius,
_ => throw new ArgumentException("Unknown shape")
};
}
public static string DescribeShape(Shape shape)
{
return shape switch
{
Circle c => $"Circle with radius {c.Radius}",
Rectangle r when r.Width == r.Height => $"Square with side {r.Width}",
Rectangle r => $"Rectangle {r.Width}x{r.Height}",
Triangle t => $"Triangle with base {t.Base} and height {t.Height}",
_ => "Unknown shape"
};
}
public static void PatternWithSwitchExpression()
{
var shapes = new Shape[]
{
new Circle("Red", 5.0),
new Rectangle("Blue", 4.0, 6.0),
new Rectangle("Green", 5.0, 5.0),
new Triangle("Yellow", 3.0, 4.0)
};
foreach (var shape in shapes)
{
var area = CalculateArea(shape);
var description = DescribeShape(shape);
Console.WriteLine($"{description}, Area: {area:F2}");
}
}
}
Relational Patterns (C# 9+):
csharp
public class RelationalPatternsDemo
{
public static string CategorizeTemperature(double temp)
{
return temp switch
{
< -10 => "Extreme Cold",
>= -10 and < 0 => "Freezing",
>= 0 and < 15 => "Cold",
>= 15 and < 25 => "Moderate",
>= 25 and < 35 => "Warm",
>= 35 => "Hot"
};
}
public static string GradeStudent(int score)
{
return score switch
{
>= 90 => "A",
>= 80 => "B",
>= 70 => "C",
>= 60 => "D",
_ => "F"
};
}
}
3. Attributes
Attributes add metadata to your code that can be read at runtime via reflection.
Detailed Examples:
Custom Attributes:
csharp
// Custom attribute for validation
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field,
AllowMultiple = false, Inherited = true)]
public class RequiredAttribute : Attribute
{
public string ErrorMessage { get; }
public RequiredAttribute(string errorMessage = "This field is required")
{
ErrorMessage = errorMessage;
}
}
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class RangeAttribute : Attribute
{
public double Min { get; }
public double Max { get; }
public string ErrorMessage { get; }
public RangeAttribute(double min, double max, string errorMessage = null)
{
Min = min;
Max = max;
ErrorMessage = errorMessage ?? $"Value must be between {min} and {max}";
}
}
[AttributeUsage(AttributeTargets.Class)]
public class DisplayNameAttribute : Attribute
{
public string Name { get; }
public DisplayNameAttribute(string name)
{
Name = name;
}
}
// Using custom attributes
[DisplayName("User Information")]
public class User
{
[Required("Username is required")]
public string Username { get; set; }
[Required]
[Range(18, 120, "Age must be between 18 and 120")]
public int Age { get; set; }
[Range(0, 100000, "Salary must be reasonable")]
public decimal Salary { get; set; }
[Obsolete("Use CreatedAt instead")]
public DateTime CreateDate { get; set; }
public DateTime CreatedAt { get; set; }
}
// Attribute for method timing
[AttributeUsage(AttributeTargets.Method)]
public class TimeExecutionAttribute : Attribute
{
public string Category { get; }
public TimeExecutionAttribute(string category = "General")
{
Category = category;
}
}
public class ServiceWithAttributes
{
[TimeExecution("Database")]
public void ProcessData()
{
Thread.Sleep(100); // Simulate work
}
[TimeExecution("API")]
[Obsolete("Use NewApiMethod instead")]
public void OldApiMethod()
{
// Old implementation
}
public void NewApiMethod()
{
// New implementation
}
}
Reading Attributes with Reflection:
csharp
public class AttributeReader
{
public static void ReadClassAttributes()
{
Type userType = typeof(User);
// Read class-level attributes
var displayNameAttr = userType.GetCustomAttribute<DisplayNameAttribute>();
if (displayNameAttr != null)
{
Console.WriteLine($"Class display name: {displayNameAttr.Name}");
}
// Read property attributes
foreach (var prop in userType.GetProperties())
{
Console.WriteLine($"\nProperty: {prop.Name}");
var requiredAttr = prop.GetCustomAttribute<RequiredAttribute>();
if (requiredAttr != null)
{
Console.WriteLine($" Required: {requiredAttr.ErrorMessage}");
}
var rangeAttr = prop.GetCustomAttribute<RangeAttribute>();
if (rangeAttr != null)
{
Console.WriteLine($" Range: {rangeAttr.Min}-{rangeAttr.Max}");
Console.WriteLine($" Error: {rangeAttr.ErrorMessage}");
}
var obsoleteAttr = prop.GetCustomAttribute<ObsoleteAttribute>();
if (obsoleteAttr != null)
{
Console.WriteLine($" Obsolete: {obsoleteAttr.Message}");
}
}
}
public static List<string> Validate(object obj)
{
var errors = new List<string>();
Type type = obj.GetType();
foreach (var prop in type.GetProperties())
{
var value = prop.GetValue(obj);
// Check Required attribute
var requiredAttr = prop.GetCustomAttribute<RequiredAttribute>();
if (requiredAttr != null)
{
if (value == null || (value is string str && string.IsNullOrEmpty(str)))
{
errors.Add(requiredAttr.ErrorMessage);
}
}
// Check Range attribute
var rangeAttr = prop.GetCustomAttribute<RangeAttribute>();
if (rangeAttr != null && value is IConvertible convertible)
{
double numericValue = convertible.ToDouble(System.Globalization.CultureInfo.InvariantCulture);
if (numericValue < rangeAttr.Min || numericValue > rangeAttr.Max)
{
errors.Add(rangeAttr.ErrorMessage);
}
}
}
return errors;
}
}
// Usage
public class AttributeDemo
{
public static void DemonstrateAttributes()
{
// Read attributes
AttributeReader.ReadClassAttributes();
// Validation using attributes
var user = new User
{
Username = "", // Invalid - empty
Age = 15, // Invalid - under 18
Salary = 50000 // Valid
};
var errors = AttributeReader.Validate(user);
Console.WriteLine("\nValidation errors:");
foreach (var error in errors)
{
Console.WriteLine($" - {error}");
}
}
}
4. Reflection
Reflection allows you to inspect and interact with types, methods, and properties at runtime.
Detailed Examples:
Basic Reflection:
csharp
public class ReflectionDemo
{
public static void InspectType(Type type)
{
Console.WriteLine($"Type: {type.FullName}");
Console.WriteLine($"Assembly: {type.Assembly.GetName().Name}");
Console.WriteLine($"Is Class: {type.IsClass}");
Console.WriteLine($"Is Public: {type.IsPublic}");
// Properties
Console.WriteLine("\nProperties:");
foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Console.WriteLine($" {prop.PropertyType.Name} {prop.Name} " +
$"(CanRead: {prop.CanRead}, CanWrite: {prop.CanWrite})");
}
// Methods
Console.WriteLine("\nMethods:");
foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
{
if (!method.IsSpecialName) // Exclude property getters/setters
{
Console.WriteLine($" {method.ReturnType.Name} {method.Name}()");
}
}
// Constructors
Console.WriteLine("\nConstructors:");
foreach (var ctor in type.GetConstructors(BindingFlags.Public | BindingFlags.Instance))
{
var parameters = ctor.GetParameters();
var paramString = string.Join(", ", parameters.Select(p => $"{p.ParameterType.Name} {p.Name}"));
Console.WriteLine($" {type.Name}({paramString})");
}
}
public static void CreateInstanceDynamically()
{
Type listType = typeof(List<>);
Type stringListType = listType.MakeGenericType(typeof(string));
// Create instance of List<string>
object stringList = Activator.CreateInstance(stringListType);
// Get Add method
MethodInfo addMethod = stringListType.GetMethod("Add");
// Add items dynamically
addMethod.Invoke(stringList, new object[] { "Hello" });
addMethod.Invoke(stringList, new object[] { "World" });
// Get Count property
PropertyInfo countProp = stringListType.GetProperty("Count");
int count = (int)countProp.GetValue(stringList);
Console.WriteLine($"Dynamically created list has {count} items: {stringList}");
}
}
Advanced Reflection:
csharp
public class AdvancedReflection
{
public static void MethodInvocationDemo()
{
var calculator = new Calculator();
Type calculatorType = calculator.GetType();
// Invoke method by name
MethodInfo addMethod = calculatorType.GetMethod("Add");
object result = addMethod.Invoke(calculator, new object[] { 5, 3 });
Console.WriteLine($"5 + 3 = {result}");
// Invoke generic method
MethodInfo genericMethod = calculatorType.GetMethod("CreateList");
MethodInfo constructedMethod = genericMethod.MakeGenericMethod(typeof(string));
object stringList = constructedMethod.Invoke(calculator, new object[] { 3 });
Console.WriteLine($"Created list: {stringList}");
}
public static void PropertyManipulation()
{
var person = new Person("John", "Doe", 30);
Type personType = typeof(Person);
// Get property value
PropertyInfo firstNameProp = personType.GetProperty("FirstName");
string firstName = (string)firstNameProp.GetValue(person);
Console.WriteLine($"First Name: {firstName}");
// Set property value (if not readonly)
PropertyInfo ageProp = personType.GetProperty("Age");
ageProp.SetValue(person, 35);
Console.WriteLine($"Updated Age: {person.Age}");
// Work with private fields (use with caution!)
FieldInfo[] privateFields = personType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
Console.WriteLine("Private fields:");
foreach (var field in privateFields)
{
Console.WriteLine($" {field.FieldType.Name} {field.Name}");
}
}
public static T CreateGenericType<T>() where T : new()
{
Type type = typeof(T);
Console.WriteLine($"Creating instance of {type.Name}");
// Inspect generic type parameters
if (type.IsGenericType)
{
Type[] genericArgs = type.GetGenericArguments();
Console.WriteLine("Generic arguments:");
foreach (var arg in genericArgs)
{
Console.WriteLine($" - {arg.Name}");
}
}
return new T();
}
}
// Supporting classes
public class Calculator
{
public int Add(int a, int b) => a + b;
public double Add(double a, double b) => a + b;
public List<T> CreateList<T>(int capacity) => new List<T>(capacity);
private string PrivateMethod() => "This is private";
}
// Usage
public class ReflectionUsage
{
public static void DemonstrateReflection()
{
// Inspect types
ReflectionDemo.InspectType(typeof(Person));
ReflectionDemo.InspectType(typeof(List<>));
// Dynamic creation
ReflectionDemo.CreateInstanceDynamically();
// Method invocation
AdvancedReflection.MethodInvocationDemo();
// Property manipulation
AdvancedReflection.PropertyManipulation();
// Generic type creation
var list = AdvancedReflection.CreateGenericType<List<string>>();
}
}
5. Dynamic Types
The dynamic keyword bypasses compile-time type checking and resolves types at runtime.
Detailed Examples:
Basic Dynamic Usage:
csharp
public class DynamicDemo
{
public static void BasicDynamicOperations()
{
// Dynamic variables
dynamic value = 10;
Console.WriteLine($"Type: {value.GetType()}, Value: {value}");
value = "Hello Dynamic World";
Console.WriteLine($"Type: {value.GetType()}, Value: {value}");
value = new List<int> { 1, 2, 3 };
Console.WriteLine($"Type: {value.GetType()}, Count: {value.Count}");
// Dynamic method calls
dynamic calculator = new Calculator();
int result = calculator.Add(5, 3); // Resolved at runtime
Console.WriteLine($"5 + 3 = {result}");
// This would throw RuntimeBinderException at runtime:
// calculator.NonExistentMethod(); // Commented out to avoid exception
}
public static void DynamicWithReflection()
{
// Using dynamic to simplify reflection
Type stringType = typeof(string);
dynamic stringInstance = Activator.CreateInstance(stringType, 'A', 5);
Console.WriteLine($"Created string: '{stringInstance}'");
// Compare with pure reflection (more verbose):
object stringObj = Activator.CreateInstance(typeof(string), new object[] { 'A', 5 });
string result = (string)stringObj; // Need explicit cast
Console.WriteLine($"With reflection: '{result}'");
}
public static void ExpandoObjectDemo()
{
// ExpandoObject - add properties/methods dynamically
dynamic person = new ExpandoObject();
// Add properties
person.Name = "John Doe";
person.Age = 30;
person.CreatedAt = DateTime.Now;
// Add method
person.GetInfo = new Func<string>(() => $"{person.Name} is {person.Age} years old");
Console.WriteLine(person.GetInfo());
// Convert to dictionary
var dict = (IDictionary<string, object>)person;
Console.WriteLine("\nProperties as dictionary:");
foreach (var kvp in dict)
{
Console.WriteLine($" {kvp.Key}: {kvp.Value}");
}
// Add new property via dictionary
dict["Email"] = "john@example.com";
Console.WriteLine($"Email: {person.Email}");
}
}
Advanced Dynamic Scenarios:
csharp
public class DynamicXmlParser
{
public static dynamic ParseXml(string xml)
{
var doc = new System.Xml.XmlDocument();
doc.LoadXml(xml);
return ParseNode(doc.DocumentElement);
}
private static dynamic ParseNode(System.Xml.XmlNode node)
{
dynamic result = new ExpandoObject();
var dict = (IDictionary<string, object>)result;
// Add attributes as properties
if (node.Attributes != null)
{
foreach (System.Xml.XmlAttribute attr in node.Attributes)
{
dict[attr.Name] = attr.Value;
}
}
// Handle child elements
foreach (System.Xml.XmlNode child in node.ChildNodes)
{
if (child.NodeType == System.Xml.XmlNodeType.Element)
{
if (!dict.ContainsKey(child.Name))
{
dict[child.Name] = ParseNode(child);
}
else
{
// Handle multiple elements with same name
if (dict[child.Name] is List<dynamic> list)
{
list.Add(ParseNode(child));
}
else
{
var existing = dict[child.Name];
dict[child.Name] = new List<dynamic> { existing, ParseNode(child) };
}
}
}
else if (child.NodeType == System.Xml.XmlNodeType.Text)
{
dict["Value"] = child.InnerText;
}
}
return result;
}
}
public class DynamicUsage
{
public static void DemonstrateDynamicXml()
{
string xml = @"
<person id='123'>
<name>John Doe</name>
<age>30</age>
<address>
<street>123 Main St</street>
<city>Springfield</city>
</address>
<hobbies>
<hobby>Reading</hobby>
<hobby>Gaming</hobby>
</hobbies>
</person>";
dynamic person = DynamicXmlParser.ParseXml(xml);
Console.WriteLine($"ID: {person.id}");
Console.WriteLine($"Name: {person.name.Value}");
Console.WriteLine($"Age: {person.age.Value}");
Console.WriteLine($"Address: {person.address.street.Value}, {person.address.city.Value}");
Console.WriteLine("Hobbies:");
foreach (var hobby in person.hobbies.hobby)
{
Console.WriteLine($" - {hobby.Value}");
}
}
}
6. Extension Methods
Extension methods allow you to "add" methods to existing types without modifying them.
Detailed Examples:
Basic Extension Methods:
csharp
public static class StringExtensions
{
// Extension method for string
public static bool IsNullOrEmpty(this string str)
{
return string.IsNullOrEmpty(str);
}
public static string Reverse(this string str)
{
if (string.IsNullOrEmpty(str))
return str;
char[] charArray = str.ToCharArray();
Array.Reverse(charArray);
return new string(charArray);
}
public static string Truncate(this string str, int maxLength)
{
if (string.IsNullOrEmpty(str) || str.Length <= maxLength)
return str;
return str.Substring(0, maxLength) + "...";
}
public static bool IsPalindrome(this string str)
{
if (string.IsNullOrEmpty(str))
return false;
string clean = new string(str.Where(char.IsLetterOrDigit).ToArray()).ToLower();
return clean == clean.Reverse();
}
}
public static class CollectionExtensions
{
public static string ToDelimitedString<T>(this IEnumerable<T> collection, string delimiter = ", ")
{
return string.Join(delimiter, collection);
}
public static IEnumerable<T> ForEach<T>(this IEnumerable<T> collection, Action<T> action)
{
foreach (var item in collection)
{
action(item);
yield return item; // Allows chaining
}
}
public static bool IsEmpty<T>(this IEnumerable<T> collection)
{
return !collection.Any();
}
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T> collection) where T : class
{
return collection.Where(item => item != null);
}
}
public static class NumericExtensions
{
public static bool IsBetween(this int value, int min, int max)
{
return value >= min && value <= max;
}
public static double ToRadians(this double degrees)
{
return degrees * Math.PI / 180.0;
}
public static double ToDegrees(this double radians)
{
return radians * 180.0 / Math.PI;
}
public static string ToFileSizeString(this long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
int order = 0;
double len = bytes;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len /= 1024;
}
return $"{len:0.##} {sizes[order]}";
}
}
Advanced Extension Methods:
csharp
public static class LINQExtensions
{
public static IEnumerable<TResult> SelectWithIndex<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, int, TResult> selector)
{
int index = 0;
foreach (var item in source)
{
yield return selector(item, index++);
}
}
public static IEnumerable<(TFirst First, TSecond Second)> Zip<TFirst, TSecond>(
this IEnumerable<TFirst> first,
IEnumerable<TSecond> second)
{
using var enum1 = first.GetEnumerator();
using var enum2 = second.GetEnumerator();
while (enum1.MoveNext() && enum2.MoveNext())
{
yield return (enum1.Current, enum2.Current);
}
}
public static TSource MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
{
return source.OrderBy(selector).First();
}
public static TSource MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> selector)
{
return source.OrderByDescending(selector).First();
}
}
public static class DateTimeExtensions
{
public static DateTime StartOfDay(this DateTime date)
{
return new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, 0);
}
public static DateTime EndOfDay(this DateTime date)
{
return new DateTime(date.Year, date.Month, date.Day, 23, 59, 59, 999);
}
public static bool IsWeekend(this DateTime date)
{
return date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday;
}
public static int Age(this DateTime birthDate)
{
var today = DateTime.Today;
int age = today.Year - birthDate.Year;
if (birthDate.Date > today.AddYears(-age)) age--;
return age;
}
}
Using Extension Methods:
csharp
public class ExtensionMethodDemo
{
public static void DemonstrateExtensions()
{
// String extensions
string text = "Hello World";
Console.WriteLine($"Original: {text}");
Console.WriteLine($"Reversed: {text.Reverse()}");
Console.WriteLine($"Truncated: {text.Truncate(5)}");
Console.WriteLine($"Is palindrome: {"radar".IsPalindrome()}");
// Collection extensions
var numbers = new List<int> { 1, 2, 3, 4, 5 };
Console.WriteLine($"Numbers: {numbers.ToDelimitedString(" | ")}");
Console.WriteLine($"Is empty: {numbers.IsEmpty()}");
// Chain extension methods
numbers
.Where(n => n > 2)
.ForEach(n => Console.WriteLine($"Processing: {n}"))
.Select(n => n * 2)
.ForEach(n => Console.WriteLine($"Doubled: {n}"));
// Numeric extensions
int value = 42;
Console.WriteLine($"42 is between 40 and 50: {value.IsBetween(40, 50)}");
long fileSize = 1024 * 1024 * 5 + 123456;
Console.WriteLine($"File size: {fileSize.ToFileSizeString()}");
// DateTime extensions
DateTime now = DateTime.Now;
Console.WriteLine($"Start of day: {now.StartOfDay()}");
Console.WriteLine($"Is weekend: {now.IsWeekend()}");
DateTime birthDate = new DateTime(1990, 1, 1);
Console.WriteLine($"Age: {birthDate.Age()}");
}
}
7. Real-World Complete Example
csharp
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Reflection;
namespace AdvancedFeaturesDemo
{
// Records for domain models
public record Product(string Name, decimal Price, string Category, int Stock);
public record Order(int Id, string CustomerName, List<Product> Products, DateTime OrderDate)
{
public decimal TotalAmount => Products.Sum(p => p.Price);
public bool IsRecent => (DateTime.Now - OrderDate).TotalDays < 7;
}
// Custom attributes
[AttributeUsage(AttributeTargets.Class)]
public class CacheableAttribute : Attribute
{
public int DurationMinutes { get; }
public CacheableAttribute(int durationMinutes = 30)
{
DurationMinutes = durationMinutes;
}
}
[AttributeUsage(AttributeTargets.Method)]
public class LogExecutionAttribute : Attribute
{
public string Level { get; }
public LogExecutionAttribute(string level = "Information")
{
Level = level;
}
}
// Service with attributes
[Cacheable(60)] // Cache for 60 minutes
public class OrderService
{
private readonly List<Order> _orders = new();
[LogExecution("Information")]
public void AddOrder(Order order)
{
_orders.Add(order);
Console.WriteLine($"Order {order.Id} added for {order.CustomerName}");
}
[LogExecution("Debug")]
public IEnumerable<Order> GetRecentOrders()
{
return _orders.Where(o => o.IsRecent);
}
public decimal GetTotalRevenue()
{
return _orders.Sum(o => o.TotalAmount);
}
}
// Extension methods
public static class OrderExtensions
{
public static IEnumerable<Order> ForCustomer(this IEnumerable<Order> orders, string customerName)
{
return orders.Where(o => o.CustomerName.Equals(customerName, StringComparison.OrdinalIgnoreCase));
}
public static IEnumerable<Order> InCategory(this IEnumerable<Order> orders, string category)
{
return orders.Where(o => o.Products.Any(p => p.Category == category));
}
public static string ToSummaryString(this Order order)
{
return $"Order #{order.Id} by {order.CustomerName}: {order.Products.Count} items, Total: ${order.TotalAmount}";
}
}
public static class AttributeExtensions
{
public static bool IsCacheable(this Type type)
{
return type.GetCustomAttribute<CacheableAttribute>() != null;
}
public static int? GetCacheDuration(this Type type)
{
return type.GetCustomAttribute<CacheableAttribute>()?.DurationMinutes;
}
}
// Main demo
public class Program
{
public static void Main()
{
Console.WriteLine("=== C# Advanced Features Demo ===\n");
// Records demo
DemonstrateRecords();
// Pattern matching demo
DemonstratePatternMatching();
// Attributes and reflection demo
DemonstrateAttributesAndReflection();
// Extension methods demo
DemonstrateExtensionMethods();
// Dynamic demo
DemonstrateDynamicFeatures();
}
static void DemonstrateRecords()
{
Console.WriteLine("1. RECORDS DEMO");
Console.WriteLine("---------------");
var products = new List<Product>
{
new Product("Laptop", 999.99m, "Electronics", 10),
new Product("Mouse", 29.99m, "Electronics", 50),
new Product("Desk", 199.99m, "Furniture", 15),
new Product("Chair", 149.99m, "Furniture", 20)
};
var order = new Order(1, "John Doe", products.Take(2).ToList(), DateTime.Now.AddDays(-1));
Console.WriteLine(order.ToSummaryString());
Console.WriteLine($"Is recent: {order.IsRecent}");
Console.WriteLine();
}
static void DemonstratePatternMatching()
{
Console.WriteLine("2. PATTERN MATCHING DEMO");
Console.WriteLine("------------------------");
object[] items = {
new Product("Laptop", 999.99m, "Electronics", 10),
42,
"Hello World",
new List<int> { 1, 2, 3 },
null
};
foreach (var item in items)
{
var description = item switch
{
Product { Price: > 500 } p => $"Expensive product: {p.Name}",
Product p => $"Product: {p.Name}",
int i when i > 30 => $"Large number: {i}",
int i => $"Small number: {i}",
string s when s.Length > 5 => $"Long string: {s}",
string s => $"Short string: {s}",
ICollection<int> list => $"List with {list.Count} items",
null => "Null value",
_ => "Unknown item"
};
Console.WriteLine($" {description}");
}
Console.WriteLine();
}
static void DemonstrateAttributesAndReflection()
{
Console.WriteLine("3. ATTRIBUTES AND REFLECTION DEMO");
Console.WriteLine("---------------------------------");
var service = new OrderService();
Type serviceType = typeof(OrderService);
// Check cacheable attribute
if (serviceType.IsCacheable())
{
int? duration = serviceType.GetCacheDuration();
Console.WriteLine($"Service is cacheable for {duration} minutes");
}
// Inspect methods with attributes
foreach (var method in serviceType.GetMethods(BindingFlags.Public | BindingFlags.Instance))
{
var logAttr = method.GetCustomAttribute<LogExecutionAttribute>();
if (logAttr != null)
{
Console.WriteLine($"Method {method.Name} has log level: {logAttr.Level}");
}
}
Console.WriteLine();
}
static void DemonstrateExtensionMethods()
{
Console.WriteLine("4. EXTENSION METHODS DEMO");
Console.WriteLine("--------------------------");
var orders = new List<Order>
{
new Order(1, "John Doe",
new List<Product> { new Product("Laptop", 999.99m, "Electronics", 10) },
DateTime.Now.AddDays(-1)),
new Order(2, "Jane Smith",
new List<Product> {
new Product("Mouse", 29.99m, "Electronics", 50),
new Product("Desk", 199.99m, "Furniture", 15)
},
DateTime.Now.AddDays(-5)),
new Order(3, "John Doe",
new List<Product> { new Product("Chair", 149.99m, "Furniture", 20) },
DateTime.Now.AddDays(-10))
};
// Use extension methods
var johnsOrders = orders.ForCustomer("John Doe");
var electronicsOrders = orders.InCategory("Electronics");
Console.WriteLine("John's orders:");
foreach (var order in johnsOrders)
{
Console.WriteLine($" {order.ToSummaryString()}");
}
Console.WriteLine("\nElectronics orders:");
foreach (var order in electronicsOrders)
{
Console.WriteLine($" {order.ToSummaryString()}");
}
Console.WriteLine();
}
static void DemonstrateDynamicFeatures()
{
Console.WriteLine("5. DYNAMIC FEATURES DEMO");
Console.WriteLine("-------------------------");
// ExpandoObject for dynamic data
dynamic report = new ExpandoObject();
report.Title = "Sales Report";
report.GeneratedAt = DateTime.Now;
report.TotalRevenue = 15000.50m;
// Add method dynamically
report.GetSummary = new Func<string>(() =>
$"{report.Title} generated at {report.GeneratedAt}: ${report.TotalRevenue}");
Console.WriteLine(report.GetSummary());
// Access as dictionary
var dict = (IDictionary<string, object>)report;
dict["Author"] = "Reporting System";
Console.WriteLine($"Report author: {report.Author}");
}
}
}
Key Advanced Features Summary
-
Records: Immutable data types with value semantics
-
Pattern Matching: Concise conditional logic based on shape and content
-
Attributes: Metadata that can be inspected at runtime
-
Reflection: Runtime type inspection and dynamic code execution
-
Dynamic Types: Late binding and dynamic object creation
-
Extension Methods: Adding methods to existing types without inheritance
These features make C# incredibly powerful for modern application development, enabling more expressive, maintainable, and flexible code.
Comments (0)
No comments yet. Be the first to comment!