Generics: Type-Safe Data Structures and Methods

 

1. Generics: Type-Safe Data Structures and Methods

Generics allow you to define classes, methods, and interfaces with a placeholder for the data type. This leads to type safety and code reusability while avoiding the need for casting and boxing.

  • Generics ensure that the data structure works with any data type while maintaining type safety.

Example: Generic Class

public class Box<T> { public T Value { get; set; } public void Display() { Console.WriteLine($"Box contains: {Value}"); } } class Program { static void Main() { Box<int> intBox = new Box<int>(); intBox.Value = 42; intBox.Display(); // Output: Box contains: 42 Box<string> strBox = new Box<string>(); strBox.Value = "Hello World"; strBox.Display(); // Output: Box contains: Hello World } }

Example: Generic Method

public class Utilities { public T Max<T>(T a, T b) where T : IComparable<T> { return a.CompareTo(b) > 0 ? a : b; } } class Program { static void Main() { Utilities utilities = new Utilities(); int maxInt = utilities.Max(5, 10); // Output: 10 string maxStr = utilities.Max("apple", "banana"); // Output: banana Console.WriteLine(maxInt); Console.WriteLine(maxStr); } }

2. Anonymous Methods: Defining Inline Methods (Precursor to Lambda Expressions)

Anonymous methods in C# allow you to create inline methods without explicitly defining them in a method body. This was a precursor to lambda expressions and is primarily used with delegates.

Example: Anonymous Method

public delegate void GreetDelegate(string message); class Program { static void Main() { // Define an anonymous method GreetDelegate greet = delegate(string message) { Console.WriteLine("Hello, " + message); }; // Invoke the anonymous method greet("World"); // Output: Hello, World } }

3. Partial Classes: Splitting a Class Definition Across Multiple Files

Partial classes allow you to split the definition of a class across multiple files. This is useful when you have a large class or auto-generated code (like from designers or code generation tools).

Example:

File 1: PersonPart1.cs

public partial class Person { public string FirstName { get; set; } public string LastName { get; set; } }

File 2: PersonPart2.cs

public partial class Person { public void Introduce() { Console.WriteLine($"Hello, my name is {FirstName} {LastName}."); } }

Main File:

class Program { static void Main() { Person person = new Person { FirstName = "John", LastName = "Doe" }; person.Introduce(); // Output: Hello, my name is John Doe. } }

4. Nullable Types: Handling Null Values with Value Types

In C#, value types like int, float, and bool cannot be assigned null by default. Nullable types allow you to assign null to these value types.

  • Nullable types are declared using the ? symbol, like int?.

Example:

class Program { static void Main() { int? nullableInt = null; if (nullableInt.HasValue) { Console.WriteLine($"Value: {nullableInt.Value}"); } else { Console.WriteLine("Value is null."); } // Using null-coalescing operator int nonNullableInt = nullableInt ?? 0; // If nullableInt is null, use 0 Console.WriteLine(nonNullableInt); // Output: 0 } }

5. Iterators and the yield Keyword: Simplifying Enumerations

Iterators are methods that use the yield keyword to return items one at a time, allowing you to easily create enumerations.

The yield return statement returns a value to the caller while maintaining the method's state, allowing for lazy evaluation.

Example:

public class NumberCollection { public IEnumerable<int> GetEvenNumbers(int limit) { for (int i = 0; i <= limit; i++) { if (i % 2 == 0) { yield return i; // yield returns one value at a time } } } } class Program { static void Main() { NumberCollection numbers = new NumberCollection(); foreach (int evenNumber in numbers.GetEvenNumbers(10)) { Console.WriteLine(evenNumber); // Output: 0 2 4 6 8 10 } } }

The yield keyword allows you to implement a stateful iteration in a clean and simple way without needing to manually manage the iteration state.

Summary of Key Points

  1. Generics allow for type-safe code that works with different types without sacrificing performance or safety.
  2. Anonymous methods provide a way to create inline methods, especially for delegate-based code. They have largely been replaced by lambda expressions but are still valid and useful.
  3. Partial classes enable splitting large classes across multiple files, which is beneficial for keeping auto-generated and hand-written code separate.
  4. Nullable types allow value types to store null, adding flexibility for scenarios where missing values must be accounted for.
  5. Iterators and the yield keyword simplify the creation of custom enumerations by allowing methods to maintain their state across multiple returns.

Post a Comment