1/83
Looks like no tags are added yet.
Name | Mastery | Learn | Test | Matching | Spaced |
---|
No study sessions yet.
What are generic types? What is their purpose?
Generic types are types that allow us to create classes, methods, and structures that work with any data type (we can think of them as templates). They use placeholders like <T>
instead of specific types.
They promote reusability because they work with multiple data types, are type safe because they don’t allow type mismatch, and improve performance.
Generic types or methods are parameterized by other types.
How are generic types written?
Generic types are written using angle brackets (<T>
) to define a type parameter.
//generic class
class Box<T>
{
private T item;
public void Store(T value) => item = value;
public T Retrieve() => item;
}
//generic method
void Print<T>(T value)
{
Console.WriteLine(value);
}
What is the advantage of using generic types?
Advantages of Generic Types
Code Reusability – Write one class/method and use it with any data type.
Type Safety – Catches type errors at compile time.
Performance – Avoids boxing/unboxing for value types.
Readability & Maintainability – Reduces code duplication.
How does List
work under the hood?
Internally, List<T>
uses a resizable array (T[]
).
When full, it creates a new array (2× size), copies elements, and replaces the old array.
What happens under the hood when we remove an item from the middle of a list?
List<T>
searches for the item and removes it by shifting the rest of the elements.
All elements after the removed item shift left by one position.
The list’s size is decreased by one.
What operations can negatively impact the List’s performance?
Frequent Insertions/Deletions in the Middle – Requires shifting of elements.
Exceeding Capacity – Causes resizing, as a new array (2× size) is allocated and copied. This also means there’s a time where 2 arrays exist in the memory.
Frequent Calls to Remove
or RemoveAt
– Shifts elements, making it slow for large lists.
Using Contains
or IndexOf
on Large Lists – big search time for unsorted lists.
Excessive TrimExcess()
Calls – Can cause unnecessary resizing and copying overhead.
What are data structures?
Data structures are ways to organize and store data for efficient access and modification. Some common data structures are: Array
, List
, Stack
, Queue
, Dictionary
, Linked List
, Tree
, Graph
, etc.
How can we implement the Add method in a simplified list?
class SimpleList<T>
{
private T[] _items;
private int _count;
public SimpleList(int capacity = 4)
{
_items = new T[capacity];
_count = 0;
}
public void Add(T item)
{
if (_count == _items.Length)
Resize(); // Expand array if full
_items[_count] = item;
_count++;
}
private void Resize()
{
T[] newArray = new T[_items.Length * 2];
for (int i = 0; i < _items.Length; i++)
{
newArray[i] = _items[i];
}
_items = newArray;
}
}
What is an indexer?
An indexer allows you to access elements of a class or struct like an array (SomeArray[2]
). It provides a way to use array-like indexing for objects. Indexers don’t always need to be integers (e.g. dictionaries can use strings as indexers).
How can we remove an item at a given index in a simplified list?
class SimpleList<T>
{
private T[] _items;
private int _count;
public SimpleList(int capacity = 4)
{
_items = new T[capacity];
_count = 0;
}
public void Add(T item)
{
if (_count == _items.Length)
Resize(); // Expand array if full
_items[_count] = item;
_count++;
}
public void RemoveAt(int index)
{
if (index < 0 || index >= _count)
throw new IndexOutOfRangeException(nameof(index));
// Shift elements to the left
for (int i = index; i < _count - 1; i++)
{
_items[i] = _items[i + 1];
}
_items[_count - 1] = default; // Clear the last item
_count--;
}
private void Resize()
{
T[] newArray = new T[_items.Length * 2];
for (int i = 0; i < _items.Length; i++)
{
newArray[i] = _items[i];
}
_items = newArray;
}
}
How can we get an item with a given index in a simplified list?
class SimpleList<T>
{
private T[] _items;
private int _count;
public SimpleList(int capacity = 4)
{
_items = new T[capacity];
_count = 0;
}
public void Add(T item)
{
if (_count == _items.Length)
Resize(); // Expand array if full
_items[_count] = item;
_count++;
}
public T Get(int index)
{
if (index < 0 || index >= _count)
throw new IndexOutOfRangeException(nameof(index));
return _items[index];
}
private void Resize()
{
T[] newArray = new T[_items.Length * 2];
for (int i = 0; i < _items.Length; i++)
{
newArray[i] = _items[i];
}
_items = newArray;
}
}
Why are generic types a crucial part of C#?
Because they act like templates that can be used for multiple types, increasing code reusability. For example:
var numbers = new SimpleList<int>();
var words = new SimpleList<string>();
var dates = new SimpleList<DateTime>();
Here we’re using the same class SimpleList
for 3 different data types.
How do we create generic types?
Generic Class
class Box<T>
{
private T _item;
public void Store(T item) => _item = item;
public T Retrieve() => _item;
}
Generic Method
public void Print<T>(T value)
{
Console.WriteLine(value);
}
Generic Interface
interface IContainer<T>
{
void Add(T item);
T Get(int index);
}
What is the purpose of the default
keyword?
The default
keyword is used to get the default value of a type:
Value Types – Returns zero for numeric types (int
, float
, etc.), false
for bool
, and null
for nullable types.
Reference Types – Returns null
for classes, arrays, and delegates.
How can we handle situations when we want to return more than one result from a method?
We can do this by using:
Tuples: Quick, simple way to return multiple values.
out
Parameters: Useful when we need to return multiple values and modify existing variables.
Custom Class/Struct: Ideal for returning structured data with named properties.
What is an algorithm?
An algorithm is a step-by-step procedure or set of rules used to solve a problem or perform a task.
Key Features:
Clear Steps – Each step must be well-defined.
Input – Takes input data.
Output – Produces a result or output.
Termination – Must eventually stop.
What are tuples?
A tuple is a data structure that can hold multiple values of different types in a single object.
Key Features:
Fixed Size – Once created, the size of a tuple cannot be changed.
Heterogeneous – Can store elements of different types (e.g., int
, string
, bool
).
Indexed – Elements can be accessed by index, starting from 0.
Tuple<string, int> person = new Tuple<string, int>("Alice", 30); // A tuple holding a name and age
How can we implement a custom tuple?
var scores = new List<int> { 85, 90, 78, 92, 88 };
Statistics<int, double> totalAndAverage = GetTotalAndAverage(scores);
Console.WriteLine("Total of scores is " + totalAndAverage.Item1);
Console.WriteLine("Average of scores is " + totalAndAverage.Item2);
public class Statistics<T1, T2>
{
public T1 Item1 { get; }
public T2 Item2 { get; }
public Statistics(T1 item1, T2 item2)
{
Item1 = item1;
Item2 = item2;
}
}
public Statistics<int, double> GetTotalAndAverage(IEnumerable<int> input)
{
if (!input.Any())
{
throw new InvalidOperationException($"The input collection cannot be empty.");
}
int total = input.Sum();
double average = input.Average();
return new Statistics<int, double>(total, average);
}
How can we use built-in tuples?
var scores = new List<int> { 85, 90, 78, 92, 88 };
var totalAndAverage = GetTotalAndAverage(scores);
Console.WriteLine("Total of scores is " + totalAndAverage.Item1);
Console.WriteLine("Average of scores is " + totalAndAverage.Item2);
public (int, double) GetTotalAndAverage(IEnumerable<int> input)
{
if (!input.Any())
{
throw new InvalidOperationException("The input collection cannot be empty.");
}
int total = input.Sum();
double average = input.Average();
return (total, average);
}
What kind of tuples are there in C#?
There are 2 types of tuples:
ValueTuple (Built-In tuples)
Tuple (regular tuple, from System.Tuple)
Feature | ValueTuple | Tuple (System.Tuple) |
---|---|---|
Type | Value type | Reference type |
Introduced in | C# 7.0 | .NET Framework 4.0 |
Naming elements | Yes, you can name the elements | No, elements are named |
Performance | Faster and more memory-efficient | Slightly slower due to reference type behavior |
Mutability | Can be mutable (non-read-only) | Immutable |
Access to items | Via | Via |
What is an ArrayList
?
An ArrayList
in C# is a collection that can store a list of objects of any type.
Key Characteristics:
Dynamic resizing: Automatically resizes as you add or remove items.
Can hold any type of object: It can hold any type, including value types (like int
) and reference types (like string
or custom objects).
Index-based access: You access elements using an index, just like arrays.
What kind of issues can ArrayList
cause?
Type Safety: ArrayList stores elements as object
type, meaning you lose type safety. You have to cast the elements back to their original types, which can lead to runtime errors if casting is done incorrectly.
Boxing and unboxing can cause performance issues when storing value types (like int
, double
) in an ArrayList
. This adds overhead.
Lack of Flexibility with Type-Specific Operations: all elements are objects, so operations on them cannot be performed until they’re cast to their original type.
No Generic Support: it cannot enforce type safety in compile time.
Resizing Costs: even though ArrayList
grows dynamically, the underlying array needs to be resized when it exceeds its capacity. This resizing operation can be costly if many elements are added, as it involves copying all existing elements into a new, larger array.
What are boxing and unboxing?
Boxing is the process of converting a value type to an object, and unboxing is converting it back.
How do we define generic methods?
Define the type parameter inside angle brackets <T>
right after the method name.
Use the type parameter in the method signature (for parameters or return types).
public T GetMaxOfThree<T>(T item1, T item2, T item3)
{
T max = item1;
if (item2.CompareTo(max) > 0)
{
max = item2;
}
if (item3.CompareTo(max) > 0)
{
max = item3;
}
return max;
}
//usage of generic method
int maxInt = GetMaxOfThree(5, 10, 3);
Console.WriteLine($"Max integer: {maxInt}"); // Output: Max integer: 10
string maxString = GetMaxOfThree("apple", "banana", "orange");
Console.WriteLine($"Max string: {maxString}"); // Output: Max string: orange
double maxDouble = GetMaxOfThree(1.2, 3.4, 2.5);
Console.WriteLine($"Max double: {maxDouble}"); // Output: Max double: 3.4
How does the compiler infer the type parameters from the context in which a method is used?
The C# compiler can infer the type parameters in generic methods based on the arguments we pass to the method when we call it — when we call a generic method and provide arguments, the compiler looks at the types of the arguments to figure out what type T
should be.
How can we define generic methods with more than one type parameter?
To define a generic method with more than one type parameter in C#, we need to specify multiple type parameters inside angle brackets (< >
), separated by commas:
public <T1, T2> methodName(T1 param1, T2 param2)
For example:
public class Program
{
public static void Main()
{
var result = GetPair(5, "apple");
Console.WriteLine($"First: {result.Item1}, Second: {result.Item2}");
}
// Method with two generic parameters T1 and T2
public static Tuple<T1, T2> GetPair<T1, T2>(T1 item1, T2 item2)
{
return new Tuple<T1, T2>(item1, item2);
}
}
How can we convert a list of one type into a list of another type?
public static List<TTarget> ConvertTo<TSource, TTarget>(this List<TSource> input)
{
var result = new List<TTarget>();
foreach (var item in input)
{
result.Add((TTarget)item); //this line
//throws a compilation error (to be handled
//later
}
return result;
}
How can we convert objects of any type into objects of any other type?
There are multiple ways we can convert an object of one type to another, such as casting, parsing, etc. One of those is also the Convert
class, and the ChangeType
method, which returns an object:
var dates = new List<DateTime> { DateTime.Today, DateTime.UtcNow };
var formatted = dates.ConvertTo<DateTime, string>("yyyy-MM-dd");
Console.WriteLine(string.Join(", ", formatted));
public static class ListExtensions
{
public static List<TTarget> ConvertTo<TSource, TTarget>(this List<TSource> input, string format = null)
{
var result = new List<TTarget>();
foreach (var item in input)
{
object converted = (typeof(TSource) == typeof(DateTime) && typeof(TTarget) == typeof(string) && format != null)
? ((DateTime)(object)item).ToString(format, CultureInfo.InvariantCulture)
: Convert.ChangeType(item, typeof(TTarget));
result.Add((TTarget)converted);
}
return result;
}
}
Can we specify only some of the type parameters when using generic functions?
No, in C# we must specify all generic type parameters when calling a generic method explicitly. However, type inference allows us to omit type parameters when the compiler can infer them from the arguments.
What is the Type
class?
The Type
class in C# (part of the System
namespace) represents metadata about a type at runtime. It provides information like the type name, methods, properties, fields, and more.
object obj = "Hello";
Type type = obj.GetType();
Console.WriteLine(type.FullName); // Output: System.String
What is the purpose of the typeof
keyword?
The typeof
keyword in C# is used to obtain a Type
object representing a compile-time type. It is commonly used for reflection, metadata inspection, and generic constraints.
Type listType = typeof(List<int>);
var methods = listType.GetMethods();
foreach (var method in methods)
Console.WriteLine(method.Name); // Lists all methods of List<int>
What is the GetType
method?
The GetType()
method in C# is an instance method that returns the Type
object representing the runtime type of the current instance. This is useful for inspecting the actual type of an object at runtime, especially when the type is not known at compile time.
Key Features of GetType()
:
Runtime Type Information: It provides the type of an object during execution.
Works on Instances: Unlike typeof
, which is used with a type name, GetType()
is called on an instance of an object.
What are type constraints? What is their purpose?
Type constraints in C# are used to restrict the types that can be used as arguments for generic type parameters. They allow you to enforce certain requirements on the types that can be passed to a generic class, method, or interface, ensuring that only types that meet specific conditions can be used.
What is the purpose of the where
keyword?
The where
keyword in C# is used to define type constraints on generic type parameters. It specifies the requirements that a type must meet in order to be used as a type argument in a generic class, method, or interface. This ensures that only types that meet certain criteria are allowed, providing type safety and enabling specific operations on those types.
How does the parameterless constructor constraint work?
The parameterless constructor constraint in C# is used with the where
keyword in generic type parameters. It ensures that the type passed as a generic argument has a public, parameterless constructor (i.e., a constructor that does not require any arguments). This constraint is useful when we need to create an instance of the type inside a generic method or class, but we cannot know the specific type at compile time.
public class Factory<T> where T : new()
{
public T CreateInstance()
{
// Creates an instance of T using the parameterless constructor
return new T();
}
}
class Program
{
static void Main()
{
var factory = new Factory<SomeClass>();
var instance = factory.CreateInstance();
Console.WriteLine(instance.GetType().Name); // Output: SomeClass
}
}
public class SomeClass
{
public SomeClass() { } // Parameterless constructor
}
How can we improve the performance of a List to which many items are added one by one?
By default, List<T>
dynamically resizes, doubling its capacity when needed. This causes frequent memory allocations and copies. That’s why if we know the approximate number of elements, we should set the initial capacity to avoid unnecessary resizing.
var list = new List<int>(10000); // Preallocate space for 10,000 items
How can we measure the time of code execution with the Stopwatch class?
We can measure code execution time using the Stopwatch
class from System.Diagnostics
:
Stopwatch stopwatch = Stopwatch.StartNew(); // Start measuring
// Code to measure
for (int i = 0; i < 1000000; i++) { }
stopwatch.Stop(); // Stop measuring
Console.WriteLine($"Elapsed time: {stopwatch.ElapsedMilliseconds} ms");
How reliable is the Stopwatch
class?
The Stopwatch
class in C# is highly reliable for measuring execution time, but its accuracy depends on hardware, system load, and timer resolution.
Best Practices for Reliable Measurements
Run the measurement multiple times and take an average to reduce anomalies.
Minimize background processes during performance testing.
Use ElapsedTicks
for high-precision timing instead of milliseconds for short measurements.
Warm up the JIT compiler before timing to avoid extra overhead in the first execution.
Why do we need constraints on the base type?
We need constraints on the base type in generics to:
Ensure Type Safety – Restrict the allowed types to those that meet specific requirements.
Enable Specific Operations – Allow using methods or properties from a required base class or interface.
Prevent Compilation Errors – Avoid runtime errors by enforcing rules at compile-time.
public class Base
{
public void Show() => Console.WriteLine("Base method");
}
public class Derived : Base { }
public class Example<T> where T : Base // T must be Base or a subclass
{
public void Execute(T obj)
{
obj.Show(); // Safe because T is guaranteed to inherit Base
}
}
class Program
{
static void Main()
{
var obj = new Example<Derived>(); // ✅ Allowed
obj.Execute(new Derived());
// var obj2 = new Example<int>(); // ❌ Error: int does not inherit Base
}
}
How can we limit the generic type arguments only to types derived from a certain base class?
To limit generic type arguments to only types derived from a specific base class, use the where T : BaseClass
constraint:
public class Example<T> where T : BaseClass
{
public void DoSomething(T obj)
{
obj.Show(); // Allowed because T is guaranteed to inherit BaseClass
}
}
When should we use the base type constraint?
When we:
Need to Call Methods/Properties from a Base Class
Want to Ensure Only Related Types Are Used
Need Polymorphism in Generic Classes/Methods
Want to Enforce Object-Oriented Design
This constraint should be used only when needed.
How can we sort Lists of holding items of various types?
If all items share a common base type, we can implement the IComparable<T>
in that type:
public class Item : IComparable<Item>
{
public string Name { get; set; }
public int Value { get; set; }
public int CompareTo(Item other) => Value.CompareTo(other.Value); // Sort by Value
}
var list = new List<Item>
{
new Item { Name = "A", Value = 3 },
new Item { Name = "B", Value = 1 },
new Item { Name = "C", Value = 2 }
};
list.Sort(); // Uses CompareTo automatically
How can we sort lists that don’t implement the ICompare
interface?
We can implement the IComparable
interface:
public class Person : IComparable<Person>
{
public string Name { get; set; }
public int Age { get; set; }
// Implement CompareTo to sort by Age
public int CompareTo(Person other) => Age.CompareTo(other.Age);
}
class Program
{
static void Main()
{
var people = new List<Person>
{
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 25 }
};
people.Sort(); // No need for a custom comparer
}
}
How can we write a custom CompareTo
function?
public class Person : IComparable<Person>
{
public string Name { get; init; }
public int YearOfBirth { get; init; }
public int CompareTo(Person other)
{
//younger people to the left, older to the right
if (YearOfBirth < other.YearOfBirth)
{
return 1;
}
else if (YearOfBirth > other.YearOfBirth)
{
return -1;
}
return 0;
}
}
How can we limit the generic type arguments only to types implementing a certain interface?
To limit the generic type arguments to types that implement a certain interface, we can use the where T : IInterface
constraint. This ensures that only types implementing the specified interface can be used as the type argument for the generic class or method:
public interface IDriveable
{
void Drive();
}
public class Car : IDriveable
{
public void Drive() => Console.WriteLine("Driving a car");
}
public class Truck : IDriveable
{
public void Drive() => Console.WriteLine("Driving a truck");
}
// A class that accepts only types that implement IDriveable
public class VehicleHandler<T> where T : IDriveable
{
public void Handle(T vehicle)
{
vehicle.Drive(); // We are guaranteed that T implements IDriveable
}
}
What type constraints allows us to limit the type argument to only numeric types?
The INumber
interface provides a way to constrain the generic type to numeric types, including int
, float
, double
, and others that implement this interface:
using System;
using System.Numerics;
public class NumericProcessor<T> where T : INumber
{
public T Add(T a, T b)
{
return a + b;
}
}
class Program
{
static void Main()
{
var processor = new NumericProcessor<int>();
Console.WriteLine(processor.Add(5, 10)); // Works for integers
var doubleProcessor = new NumericProcessor<double>();
Console.WriteLine(doubleProcessor.Add(5.5, 10.5)); // Works for doubles
}
}
What is the generic math feature?
The Generic Math feature, introduced in .NET 7, provides a set of generic interfaces and methods that enable us to work with numeric types in a generic way. This feature makes it possible to define mathematical operations that are agnostic to the specific numeric type (such as int
, float
, double
, decimal
, etc.) while still ensuring type safety.
How can we define multiple constraints for a single type parameter?
We can do it like this:
public class MyClass<T>
where T : class, IMyInterface, new()
{
// Your code here
}
In this example, the type parameter T
is constrained to:
Be a reference type (class
).
Implement the interface IMyInterface
.
Have a parameterless constructor (new()
).
How can we manage constraints for multiple type parameters?
You can manage constraints for multiple type parameters in C# by applying constraints for each type parameter separately using the where
keyword. This allows you to specify different constraints for each type parameter in a generic class or method.
public class MyClass<T1, T2>
where T1 : class, new()
where T2 : struct, IComparable<T2>
{
// Your code here
}
How can we assign methods to variables?
By using Func
or Action
delegate types:
Func<int, bool> isPositive = num => num > 0;
What is a Func
?
A delegate that holds a method with parameters and a return value.
Func<int, string> intToString = num => num.ToString();
What does the last type in a Func
definition represent?
The return type of the function:
Func<int, bool> // bool is the return type
What does Func<int, DateTime, string, decimal>
represent?
A function with int
, DateTime
, and string
parameters returning a decimal
.
What is an Action
?
A delegate that holds a method with parameters but no return value (void
).
Action<string> printMessage = msg => Console.WriteLine(msg);
What does Action<string, string, bool>
represent?
A method that takes two strings and a bool, returns nothing.
Action<string, string, bool> logDetails;
What is the key difference between Func
and Action
?
Func
returns a value; Action
does not.
How do Funcs help reduce code duplication?
They let us pass different logic as functions, so we can reuse the same code for various conditions:
bool ContainsMatch(string[] words, Func<string, bool> condition)
{
foreach (var word in words)
if (condition(word)) return true;
return false;
}
How can we check if any string in an array meets a certain condition using a Func
?
By passing a method matching Func<string, bool>
as a condition to a reusable method:
bool IsLongWord(string word) => word.Length > 5;
bool hasLongWord = ContainsMatch(new[] { "apple", "banana", "pear" }, IsLongWord);
// where the ContainsMatch function is:
bool ContainsMatch(string[] words, Func<string, bool> condition)
{
foreach (var word in words)
if (condition(word)) return true;
return false;
}
How can we use a method that checks a condition on strings with a Func<string, bool>
?
By assigning the method to a Func
variable or passing it directly to another method expecting that delegate:
bool IsUppercase(string s) => s == s.ToUpper();
Func<string, bool> checkUppercase = IsUppercase;
Console.WriteLine(ContainsMatch(new[] { "Hello", "WORLD", "test" }, checkUppercase));
What are Lambda expressions in C#?
Short, anonymous functions defined inline, often used for simple logic:
var isPositive = numbers.Any(x => x > 0);
How does the compiler determine the types of parameters in a Lambda expression?
The types are inferred from the delegate or expression context where the Lambda is used:
Func<string, bool> startsWithA = s => s.StartsWith("A");
// Compiler knows 's' is a string because of Func<string, bool>
What is the syntax of a simple Lambda expression?
(parameters) => expression
or parameter => expression
for single param:
Func<int, bool> isPositive = x => x > 0;
Can Lambda expressions have multiple parameters? How?
Yes, list parameters in parentheses separated by commas:
Func<int, int, int> add = (x, y) => x + y;
What is the difference between expression-bodied and statement-bodied Lambdas?
Expression-bodied return the result of a single expression; statement-bodied use braces and can have multiple statements.
Func<int, int> square = x => x * x; // Expression-bodied
Action<string> greet = name => { Console.WriteLine("Hi " + name); }; // Statement-bodied
Can Lambda expressions capture variables from the outer scope?
Yes, they can use variables declared outside their body (closure):
int threshold = 10;
Func<int, bool> isGreater = x => x > threshold;
How do Lambda expressions relate to delegates in C#?
Lambdas are shorthand for creating delegate instances (like Func
or Action
):
Func<int, int> triple = x => x * 3; // Delegate created from Lambda
Can you assign a Lambda expression to a variable?
Yes, by assigning it directly to a Func
or Action
variable:
Func<string, bool> startsWithS = s => s.StartsWith("S");
How do you write a Lambda expression that returns a value?
By using an expression or return statement in a statement body:
Func<int, int> doubleIt = n => n * 2;
How do you write a Lambda expression that does not return a value (void)?
Use an Action
delegate with a statement-bodied Lambda:
Action<string> print = msg => Console.WriteLine(msg);
What types in C# commonly use Lambda expressions as parameters?
Delegates like Func<>
, Action<>
, and LINQ methods like Where
or Select
:
var evens = numbers.Where(n => n % 2 == 0);
Are Lambda expressions compiled into named methods or anonymous methods?
They compile into anonymous methods (closures) behind the scenes.
What is a delegate in C#?
A delegate is a type that holds references to methods with a specific signature:
delegate int MathOperation(int x, int y);
How do you assign methods to a delegate variable?
By assigning any method with a matching signature to the delegate variable:
delegate int MathOperation(int x, int y);
int Add(int a, int b) => a + b;
MathOperation op = Add;
How do you invoke a method through a delegate?
By calling the delegate variable like a method:
delegate int MathOperation(int x, int y);
int Add(int a, int b) => a + b;
MathOperation op = Add;
int result = op(5, 3); // Calls Add(5, 3)
What is the difference between delegates and Funcs or Actions?
Funcs and Actions are built-in generic delegates; delegates are user-defined types:
Func<int, int, int> multiply = (x, y) => x * y;
What are multicast delegates?
Delegates that hold references to multiple methods and invoke them all when called:
delegate void Notify(string message);
Notify notify1 = msg => Console.WriteLine(msg.ToUpper());
Notify notify2 = msg => Console.WriteLine(msg.ToLower());
Notify multicastNotify = notify1 + notify2;
multicastNotify("Hello!");
// Outputs:
// HELLO!
// hello!
How do you add more methods to a multicast delegate?
By using the +=
operator to chain methods:
delegate void Notify(string message);
Notify notify1 = msg => Console.WriteLine(msg.ToUpper());
Notify notify2 = msg => Console.WriteLine(msg.ToLower());
Notify multicastNotify = notify1 + notify2;
multicastNotify("Hello!");
// Outputs:
// HELLO!
// hello!
Notify notify3 = msg => Console.WriteLine(msg.Length);
multicastNotify += notify3;
multicastNotify("Hello!");
// Outputs:
// HELLO!
// hello!
// 6
What is a delegate signature?
The return type and parameters that a delegate represents.
delegate bool Compare(int a, int b); // Signature: bool(int, int)
Can you combine multicast delegates with return types?
Multicast delegates with return types only return the result of the last method invoked:
delegate int Operation(int x);
Operation op = x => x + 1;
op += x => x * 2;
int result = op(3); // result = 6 (from last method)
How do you remove a method from a multicast delegate?
By using the -=
operator:
op -= x => x * 2;
Can you use anonymous methods instead of Lambda expressions?
Yes, by using the delegate
keyword for inline methods:
Func<int, int> square = delegate(int x) { return x * x; };
What is a closure in relation to Lambda expressions?
A Lambda that captures variables from the surrounding scope:
int factor = 2;
Func<int, int> multiply = x => x * factor; // captures 'factor'
Can Funcs, Actions, and delegates be used to implement events?
Yes, delegates are the basis for events in C#:
public delegate void Notify(string message);
public event Notify OnNotify;
What happens if a multicast delegate has void return type and one of the methods throws an exception?
Invocation stops at the exception; remaining methods aren’t called.