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, likeint?
.
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
- Generics allow for type-safe code that works with different types without sacrificing performance or safety.
- 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.
- Partial classes enable splitting large classes across multiple files, which is beneficial for keeping auto-generated and hand-written code separate.
- Nullable types allow value types to store
null
, adding flexibility for scenarios where missing values must be accounted for. - Iterators and the
yield
keyword simplify the creation of custom enumerations by allowing methods to maintain their state across multiple returns.