Advanced C# Concepts
This section explores some of the more advanced and sophisticated features of the C# language and .NET platform. Mastering these concepts can significantly enhance your ability to write efficient, maintainable, and powerful applications.
1. Generics
Generics provide a way to define type-safe collections and methods without sacrificing performance or type safety. They allow you to write code that operates on a set of types without knowing the specific type at compile time.
Benefits of Generics
- Type Safety: Prevents runtime type errors by enforcing type constraints at compile time.
- Performance: Avoids boxing and unboxing overhead associated with non-generic collections.
- Code Reusability: Write a single class or method that can work with any type.
For example, the generic List<T> collection provides a type-safe way to manage lists of items:
List<int> numbers = new List<int>();
numbers.Add(10);
// numbers.Add("hello"); // This would cause a compile-time error
List<string> names = new List<string>();
names.Add("Alice");
names.Add("Bob");
2. Delegates and Events
Delegates are type-safe function pointers that enable callback mechanisms. Events are built upon delegates and provide a publisher-subscriber pattern for communication between objects.
Delegates
A delegate defines the signature of a method. You can then create instances of delegates that point to any method with a compatible signature.
public delegate void MyDelegate(string message);
public class Publisher
{
public MyDelegate Handler;
public void DoSomething()
{
if (Handler != null)
{
Handler("Operation completed!");
}
}
}
Events
Events are declared using the event keyword, which is essentially a restricted delegate. They are commonly used for UI interactions and asynchronous operations.
public class MyClass
{
public event EventHandler MyCustomEvent;
protected virtual void OnMyCustomEvent(EventArgs e)
{
MyCustomEvent?.Invoke(this, e);
}
public void TriggerEvent()
{
OnMyCustomEvent(EventArgs.Empty);
}
}
3. Extension Methods
Extension methods allow you to add new methods to existing types without modifying their source code. This is particularly useful for extending framework types or adding utility functions.
To create an extension method, you define a static class and a static method. The first parameter of the method must be of the type you want to extend, and it must be preceded by the this keyword.
public static class StringExtensions
{
public static int WordCount(this string str)
{
if (string.IsNullOrEmpty(str))
return 0;
return str.Split(new[] { ' ', '.', '?' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
// Usage:
string text = "This is a sample sentence.";
int count = text.WordCount(); // Output: 4
4. LINQ (Language Integrated Query)
LINQ provides a consistent, powerful, and declarative way to query data from various sources, including in-memory collections, databases, XML, and more. It allows you to write queries using a syntax similar to SQL directly within your C# code.
Key LINQ Concepts
- Query Syntax: Uses keywords like
from,where,select. - Method Syntax: Uses extension methods like
Where(),Select(),OrderBy(). - Deferred Execution: Queries are not executed until the results are actually iterated over.
Example of LINQ query syntax:
var numbers = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evenNumbers = from num in numbers
where num % 2 == 0
orderby num descending
select num;
foreach (var n in evenNumbers)
{
Console.WriteLine(n); // Output: 10, 8, 6, 4, 2
}
5. Async and Await
Asynchronous programming with async and await allows you to write non-blocking code, improving application responsiveness, especially for I/O-bound operations like network requests or file access.
The async keyword marks a method as asynchronous, and the await keyword is used to pause the execution of the method until an awaited asynchronous operation completes.
public async Task<string> DownloadStringAsync(string url)
{
using (HttpClient client = new HttpClient())
{
return await client.GetStringAsync(url);
}
}
// Usage:
public async Task LoadData()
{
string content = await DownloadStringAsync("https://example.com");
Console.WriteLine("Data downloaded successfully.");
}
6. Reflection
Reflection allows you to inspect and manipulate the metadata of types at runtime. You can use it to discover properties, methods, and fields of an object, dynamically create instances, and invoke methods.
Reflection is a powerful tool but can have performance implications, so it's best used judiciously.
Type personType = typeof(Person);
var properties = personType.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"Property: {prop.Name}, Type: {prop.PropertyType.Name}");
}
// Assume 'person' is an instance of Person
object personInstance = Activator.CreateInstance(personType);
var nameProperty = personType.GetProperty("Name");
nameProperty.SetValue(personInstance, "John Doe");