Module 2 · Object-oriented C#, delegates and generics · Lesson 3 of 15
OOP, delegates, generics, and reflection
Question 31. Interface vs abstract class?
Answer: An interface defines a capability/contract that unrelated classes can implement. An abstract class can share state and implementation among related classes.
public interface IEmailSender
{
Task SendAsync(string to, string subject, string body, CancellationToken ct);
}
public abstract class ReportExporter
{
protected string FormatHeader() => "Report";
public abstract byte[] Export();
}Tip: Prefer interfaces at boundaries; use abstract classes when there is real shared base behavior.
Question 32. Why can a public class not derive from an internal base class?
Answer: Public APIs cannot expose less-accessible types. If a public class derived from an internal base type, external callers would see a public type whose inheritance contract depends on a type they cannot access.
internal class InternalBase { }
// Invalid if exposed publicly:
// public class PublicChild : InternalBase { }Question 33. What are delegates, events, Func, Action, and Predicate?
Answer: A delegate is a type-safe reference to a method. Func returns a value, Action returns void, and Predicate<T> returns bool. An event wraps delegate subscription so outside code can add/remove handlers but not invoke the event.
Func<int, int, int> add = (x, y) => x + y; Action<string> log = message => Console.WriteLine(message); Predicate<int> isEven = x => x % 2 == 0; public event EventHandler<OrderCreatedEventArgs>? OrderCreated;
Tip: Use events for notifications and delegates/functions for pluggable behavior.
Question 34. Can Func use out parameters?
Answer: No. Built-in Func and Action delegate types do not support ref, out, or in parameter modifiers. Declare a custom delegate.
TryParseDelegate parser = int.TryParse;
bool ok = parser("123", out int value);
public delegate bool TryParseDelegate(string input, out int value);Question 35. What happens if one handler in a multicast delegate throws?
Answer: Invocation stops at the throwing handler and the exception propagates to the caller. Later handlers are not invoked unless you manually iterate over the invocation list and handle exceptions.
foreach (EventHandler handler in MyEvent?.GetInvocationList() ?? Array.Empty<Delegate>())
{
try { handler(this, EventArgs.Empty); }
catch (Exception ex) { Log(ex); }
}Tip: For normal events, keep handlers small and resilient.
Question 36. What are ref, out, and in parameters?
Answer: ref passes a variable by reference and must be initialized before the call. out passes by reference and must be assigned by the callee before returning. in passes by readonly reference, useful for large structs.
void Increment(ref int x) => x++;
bool TryGet(out int value)
{
value = 42;
return true;
}
void Print(in BigStruct data)
{
Console.WriteLine(data.Id);
}Correction: in prevents reassigning the parameter variable. It does not make a referenced object's internal state immutable.
Question 37. What are generics and why use them?
Answer: Generics let you write type-safe reusable code without boxing or unsafe casts.
public sealed class Repository<T>
{
private readonly List<T> _items = new();
public void Add(T item) => _items.Add(item);
public IReadOnlyList<T> Items => _items;
}Tip: Generics are heavily used in collections, LINQ, dependency injection, async APIs, and repositories.
Question 38. What is covariance and contravariance?
Answer: Covariance allows a more derived output type to be used where a base output type is expected. Contravariance allows a less derived input type where a derived input type is expected.
IEnumerable<string> strings = new List<string>(); IEnumerable<object> objects = strings; // covariance: out T Action<object> handleObject = o => Console.WriteLine(o); Action<string> handleString = handleObject; // contravariance: in T
Interview tip: IEnumerable<out T> is covariant. Action<in T> is contravariant. List<T> itself is invariant.
Question 39. Why does IList<IList<int>> not accept List<List<int>>?
Answer: Generic collections like IList<T> are invariant because they allow both reading and writing. If assignment were allowed, code could insert a different implementation of IList<int> into a List<List<int>>, breaking type safety.
List<List<int>> concrete = new(); IEnumerable<IList<int>> readOnlyView = concrete; // ok through covariance of IEnumerable
Tip: Use IEnumerable<IList<int>> or create List<IList<int>> from the start when you need interface-typed nested lists.
Question 40. Why must Equals and GetHashCode be overridden together?
Answer: Hash-based collections use hash code to find a bucket and equality to confirm the match. If two objects are equal, they must return the same hash code.
public sealed record EmployeeKey(int CompanyId, int EmployeeId);
Tip: Records generate value equality and hash code automatically. Do not mutate equality fields while an object is used as a dictionary key.
Question 41. Is a hash code unique?
Answer: No. Hash codes are not unique identifiers. Different values can produce the same hash code, called a collision. Collections handle collisions by checking equality inside a bucket.
var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
set.Add("ADMIN");
Console.WriteLine(set.Contains("admin")); // trueQuestion 42. Why do hash algorithms often use prime numbers?
Answer: Prime numbers can help spread values across buckets and reduce clustering for simple hash-combination strategies. In modern C#, prefer HashCode.Combine rather than hand-rolled prime math.
public override int GetHashCode() => HashCode.Combine(CompanyId, EmployeeId);
Question 43. What is [Serializable] and when should it be used?
Answer: [Serializable] marks a type as serializable for legacy binary/soap serialization patterns. For normal JSON serialization (System.Text.Json, Newtonsoft.Json), it is generally not required.
[Serializable]
public sealed class LegacyMessage
{
public string Text { get; set; } = "";
}Correction: Do not add [Serializable] thinking it helps JSON APIs. Use DTOs and serializer attributes/options for JSON.
Question 44. What does [Flags] do on an enum?
Answer: [Flags] indicates that enum values can be combined as bit flags. Values should be powers of two.
var permissions = Permissions.Read | Permissions.Write;
bool canWrite = permissions.HasFlag(Permissions.Write);
[Flags]
public enum Permissions
{
None = 0,
Read = 1,
Write = 2,
Delete = 4
}Question 45. What are attributes and how do you read them?
Answer: Attributes attach metadata to code. You can read them with reflection.
var method = typeof(OrderService).GetMethod(nameof(OrderService.CreateOrder));
var audit = method?.GetCustomAttributes(typeof(AuditAttribute), false)
.Cast<AuditAttribute>()
.FirstOrDefault();
Console.WriteLine(audit?.Action); // Create order
[AttributeUsage(AttributeTargets.Method)]
public sealed class AuditAttribute : Attribute
{
public string Action { get; }
public AuditAttribute(string action) => Action = action;
}
public sealed class OrderService
{
[Audit("Create order")]
public void CreateOrder() { }
}Question 46. How costly is reflection and when is it appropriate?
Answer: Reflection is slower than direct calls because it works with metadata and dynamic invocation. It is appropriate for frameworks, serializers, dependency injection containers, plugin loading, attributes, mapping, and tooling. Cache reflection results if used repeatedly.
private static readonly PropertyInfo[] CustomerProperties =
typeof(Customer).GetProperties();Tip: Reflection cost is usually fine at startup or configuration time, but avoid repeated reflection in hot loops.
Question 47. What is Activator.CreateInstance?
Answer: It creates an object dynamically when the type is known only at runtime.
Type type = typeof(StringBuilder); object instance = Activator.CreateInstance(type)!;
Tip: Prefer dependency injection or factories for normal application code. Use Activator for framework/plugin scenarios.
Question 48. What is a DLL vs an EXE?
Answer: An EXE is an executable entry point. A DLL is a reusable library loaded by an executable or another library. In .NET, both are assemblies containing metadata and IL/native code depending on build/runtime.
Example: A web API project may compile to an executable host and reference several DLL class libraries for services, data access, and domain logic.
Question 49. What is a design pattern?
Answer: A design pattern is a reusable solution shape for a recurring design problem. Patterns are not code templates to blindly copy; they are vocabulary for trade-offs.
Examples: Factory for object creation, Adapter for incompatible interfaces, Strategy for replaceable algorithms, Repository for data-access boundary, Decorator for adding behavior around an object.