10 C# Generics Interview Questions and Answers
Prepare for your C# interview with this guide on Generics. Enhance your understanding and showcase your expertise with curated questions and answers.
Prepare for your C# interview with this guide on Generics. Enhance your understanding and showcase your expertise with curated questions and answers.
C# Generics provide a powerful way to define classes, methods, and data structures with a placeholder for the type of data they store or use. This feature enhances code reusability, type safety, and performance by allowing developers to create more flexible and maintainable code. Generics are a fundamental aspect of C# that can significantly optimize the development process, making them a crucial topic for any C# developer to master.
This article offers a curated selection of interview questions focused on C# Generics, designed to help you deepen your understanding and demonstrate your expertise. By familiarizing yourself with these questions and their answers, you will be better prepared to tackle technical interviews and showcase your proficiency in this essential area of C#.
Generics in C# enable defining classes, methods, and data structures that can operate with any data type while maintaining type safety. This promotes code reusability and reduces the need for type casting.
Here is an example of a generic class in C#:
public class GenericStorage<T> { private T data; public void SetData(T value) { data = value; } public T GetData() { return data; } }
To instantiate this generic class with different types, you can do the following:
// Instantiate with an integer GenericStorage<int> intStorage = new GenericStorage<int>(); intStorage.SetData(42); int intData = intStorage.GetData(); // Instantiate with a string GenericStorage<string> stringStorage = new GenericStorage<string>(); stringStorage.SetData("Hello, World!"); string stringData = stringStorage.GetData();
A generic method can compare two parameters of the same type and return the greater one. Here’s an example:
public static T Max<T>(T a, T b) where T : IComparable<T> { return a.CompareTo(b) > 0 ? a : b; }
The Max
method uses a generic type T
with a constraint ensuring T
implements IComparable<T>
, allowing the use of CompareTo
.
Here is an example of creating a generic interface and implementing it in a class:
// Define a generic interface public interface IRepository<T> { void Add(T item); T Get(int id); } // Implement the generic interface in a class public class Repository<T> : IRepository<T> { private List<T> _items = new List<T>(); public void Add(T item) { _items.Add(item); } public T Get(int id) { return _items[id]; } } // Usage public class Program { public static void Main() { IRepository<string> stringRepository = new Repository<string>(); stringRepository.Add("Hello"); Console.WriteLine(stringRepository.Get(0)); // Output: Hello IRepository<int> intRepository = new Repository<int>(); intRepository.Add(42); Console.WriteLine(intRepository.Get(0)); // Output: 42 } }
Here is an example of a generic class with multiple constraints:
public interface IExampleInterface { void ExampleMethod(); } public class BaseClass { public void BaseMethod() { } } public class GenericClass<T> where T : BaseClass, IExampleInterface, new() { public T Instance { get; set; } public GenericClass() { Instance = new T(); } public void Execute() { Instance.BaseMethod(); Instance.ExampleMethod(); } }
In this example, GenericClass
has a type parameter T
with constraints: it must inherit from BaseClass
, implement IExampleInterface
, and have a parameterless constructor.
Nested generic classes are useful for creating complex data structures or organizing code modularly. Here’s an example:
public class OuterClass<T> { public class InnerClass<U> { public T OuterValue { get; set; } public U InnerValue { get; set; } public InnerClass(T outerValue, U innerValue) { OuterValue = outerValue; InnerValue = innerValue; } } } // Instantiating the nested generic class OuterClass<int>.InnerClass<string> instance = new OuterClass<int>.InnerClass<string>(42, "Hello");
In this example, OuterClass<T>
contains a nested generic class InnerClass<U>
, demonstrating instantiation with specified types.
A generic delegate can operate with any data type. Here’s an example:
// Define a generic delegate public delegate T Operation<T>(T a, T b); class Program { // Method that matches the delegate signature public static int Add(int a, int b) { return a + b; } public static void Main() { // Instantiate the generic delegate with int type Operation<int> addOperation = Add; // Use the delegate int result = addOperation(5, 3); Console.WriteLine(result); // Output: 8 } }
In this example, Operation<T>
is a generic delegate used with different data types, demonstrated by the Add
method.
Here is an example of a generic extension method for a collection:
using System; using System.Collections.Generic; using System.Linq; public static class CollectionExtensions { public static IEnumerable<T> Filter<T>(this IEnumerable<T> source, Func<T, bool> predicate) { foreach (var item in source) { if (predicate(item)) { yield return item; } } } } // Usage class Program { static void Main() { List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6 }; var evenNumbers = numbers.Filter(n => n % 2 == 0); foreach (var number in evenNumbers) { Console.WriteLine(number); } } }
Generic constraints allow specifying requirements for a type argument. Here’s an example using interfaces:
public interface IPrintable { void Print(); } public class Document : IPrintable { public void Print() { Console.WriteLine("Printing Document"); } } public class Report : IPrintable { public void Print() { Console.WriteLine("Printing Report"); } } public class Printer<T> where T : IPrintable { public void PrintDocument(T document) { document.Print(); } } class Program { static void Main() { Printer<Document> documentPrinter = new Printer<Document>(); documentPrinter.PrintDocument(new Document()); Printer<Report> reportPrinter = new Printer<Report>(); reportPrinter.PrintDocument(new Report()); } }
In this example, Printer<T>
uses a constraint to ensure T
implements IPrintable
, allowing PrintDocument
to call Print
.
LINQ leverages generic collections to provide querying capabilities for various data sources. By using generics, LINQ can work with any collection implementing IEnumerable<T>
, allowing for type-safe queries.
Here’s an example demonstrating LINQ with a generic List<T>
:
using System; using System.Collections.Generic; using System.Linq; class Program { static void Main() { List<int> numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; var evenNumbers = numbers.Where(n => n % 2 == 0).ToList(); foreach (var num in evenNumbers) { Console.WriteLine(num); } } }
In this example, the Where
method filters elements of the generic List<int>
based on a condition, resulting in a list of even numbers.
Generics can significantly improve code quality by reducing redundancy and increasing reusability. They also enhance performance by eliminating the need for type casting and boxing/unboxing operations.
A real-world scenario where generics improve code quality and performance is in implementing a type-safe collection, such as a list. Without generics, you would have to use a non-generic collection like ArrayList
, which stores elements as objects, requiring type casting and potentially leading to runtime errors.
Example:
using System; using System.Collections.Generic; public class Program { public static void Main() { List<int> numbers = new List<int>(); numbers.Add(1); numbers.Add(2); numbers.Add(3); foreach (int number in numbers) { Console.WriteLine(number); } } }
In this example, List<int>
is a generic collection that ensures type safety, allowing only integers and eliminating the need for type casting.