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

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

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
}
Tip: While both query and method syntax are powerful, method syntax often offers more flexibility and chaining capabilities for complex operations.

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");