Generic Types & Advanced Use of Methods In C#

5.0(1)
studied byStudied by 3 people
learnLearn
examPractice Test
spaced repetitionSpaced Repetition
heart puzzleMatch
flashcardsFlashcards
Card Sorting

1/83

encourage image

There's no tags or description

Looks like no tags are added yet.

Study Analytics
Name
Mastery
Learn
Test
Matching
Spaced

No study sessions yet.

84 Terms

1
New cards

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.

2
New cards

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);  
}

3
New cards

What is the advantage of using generic types?

Advantages of Generic Types

  1. Code Reusability – Write one class/method and use it with any data type.

  2. Type Safety – Catches type errors at compile time.

  3. Performance – Avoids boxing/unboxing for value types.

  4. Readability & Maintainability – Reduces code duplication.

4
New cards

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.

5
New cards

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.

6
New cards

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 Listsbig search time for unsorted lists.

  • Excessive TrimExcess() Calls – Can cause unnecessary resizing and copying overhead.

7
New cards

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.

8
New cards

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;
    }
}

9
New cards

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).

10
New cards

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;
    }
}

11
New cards

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;
    }
}

12
New cards

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.

13
New cards

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);
    }

14
New cards

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.

15
New cards

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.

16
New cards

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:

  1. Clear Steps – Each step must be well-defined.

  2. Input – Takes input data.

  3. Output – Produces a result or output.

  4. Termination – Must eventually stop.

17
New cards

What are tuples?

A tuple is a data structure that can hold multiple values of different types in a single object.

Key Features:

  1. Fixed Size – Once created, the size of a tuple cannot be changed.

  2. Heterogeneous – Can store elements of different types (e.g., int, string, bool).

  3. 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

18
New cards

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);
}

19
New cards

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);
}

20
New cards

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 Item1, Item2, etc.

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 Item1, Item2, ... or by name

Via Item1, Item2, ...

21
New cards

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.

22
New cards

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.

23
New cards

What are boxing and unboxing?

Boxing is the process of converting a value type to an object, and unboxing is converting it back.

24
New cards

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

25
New cards

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.

26
New cards

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);
    }
}

27
New cards

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;
}

28
New cards

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;
    }
}

29
New cards

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.

30
New cards

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

31
New cards

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>

32
New cards

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():

  1. Runtime Type Information: It provides the type of an object during execution.

  2. Works on Instances: Unlike typeof, which is used with a type name, GetType() is called on an instance of an object.

33
New cards

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.

34
New cards

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.

35
New cards

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
}

36
New cards

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

37
New cards

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");

38
New cards

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.

39
New cards

Why do we need constraints on the base type?

We need constraints on the base type in generics to:

  1. Ensure Type Safety – Restrict the allowed types to those that meet specific requirements.

  2. Enable Specific Operations – Allow using methods or properties from a required base class or interface.

  3. 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
    }
}

40
New cards

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
    }
}

41
New cards

When should we use the base type constraint?

When we:

  1. Need to Call Methods/Properties from a Base Class

  2. Want to Ensure Only Related Types Are Used

  3. Need Polymorphism in Generic Classes/Methods

  4. Want to Enforce Object-Oriented Design

This constraint should be used only when needed.

42
New cards

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

43
New cards

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
    }
}

44
New cards

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;
    }
}

45
New cards

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
    }
}

46
New cards

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
    }
}

47
New cards

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.

48
New cards

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:

  1. Be a reference type (class).

  2. Implement the interface IMyInterface.

  3. Have a parameterless constructor (new()).

49
New cards

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
}

50
New cards

How can we assign methods to variables?

By using Func or Action delegate types:

Func<int, bool> isPositive = num => num > 0;

51
New cards

What is a Func?

A delegate that holds a method with parameters and a return value.

Func<int, string> intToString = num => num.ToString();

52
New cards

What does the last type in a Func definition represent?

The return type of the function:

Func<int, bool> // bool is the return type

53
New cards

What does Func<int, DateTime, string, decimal> represent?

A function with int, DateTime, and string parameters returning a decimal.

54
New cards

What is an Action?

A delegate that holds a method with parameters but no return value (void).

Action<string> printMessage = msg => Console.WriteLine(msg);

55
New cards

What does Action<string, string, bool> represent?

A method that takes two strings and a bool, returns nothing.

Action<string, string, bool> logDetails;

56
New cards

What is the key difference between Func and Action?

Func returns a value; Action does not.

57
New cards

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;
}

58
New cards

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;
}

59
New cards

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));

60
New cards

What are Lambda expressions in C#?

Short, anonymous functions defined inline, often used for simple logic:

var isPositive = numbers.Any(x => x > 0);

61
New cards

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>

62
New cards

What is the syntax of a simple Lambda expression?

(parameters) => expression or parameter => expression for single param:

Func<int, bool> isPositive = x => x > 0;

63
New cards

Can Lambda expressions have multiple parameters? How?

Yes, list parameters in parentheses separated by commas:

Func<int, int, int> add = (x, y) => x + y;

64
New cards

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

65
New cards

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;

66
New cards

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

67
New cards

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");

68
New cards

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;

69
New cards

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);

70
New cards

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);

71
New cards

Are Lambda expressions compiled into named methods or anonymous methods?

They compile into anonymous methods (closures) behind the scenes.

72
New cards

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);

73
New cards

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;

74
New cards

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)

75
New cards

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;

76
New cards

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!

77
New cards

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

78
New cards

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)

79
New cards

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)

80
New cards

How do you remove a method from a multicast delegate?

By using the -= operator:

op -= x => x * 2;

81
New cards

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; };

82
New cards

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'

83
New cards

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;

84
New cards

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.