Method Group in C#

Bilal Shahid Nov 30, 2023
  1. Delegates in C#
  2. Method Groups in C#
  3. Method Groups Conversion With Delegate in C#
  4. Simple Custom Delegate for Method Groups in C#
  5. Use Cases and Benefits
  6. Binding Method Groups to Delegates
  7. Delegate Invocation with Method Groups
  8. Conclusion
Method Group in C#

In C#, a method group is a fundamental concept that facilitates delegate binding, aiding in the creation of more flexible and dynamic code. Method groups serve as a reference to one or more methods with a compatible signature, enabling a simplified way to handle functions as objects.

This article aims to delve into the nuances of method groups, their syntax, usage, and significance within the C# programming language.

Delegates in C#

Before diving into method groups, it’s crucial to grasp the essence of delegates. Delegates in C# act as type-safe function pointers, allowing the invocation of methods indirectly.

They provide a way to treat methods as entities that can be assigned, passed as parameters, and invoked dynamically.

Delegates are pointers to functions created. Along with pointing to the function, it also defines the return type of the functions and their parameters.

A delegate, let’s say, called LAPTOP, can have a return type as void and a parameter as USERNAME (string). We have another function called PC, which also has the same return type and parameter type as LAPTOP.

We can call PC from the delegate LAPTOP in this scenario.

Code Example:

using System;

namespace ConsoleApplication1 {
  class Program {
    delegate void LAPTOP(string username);

    static void PC(string username) {
      Console.WriteLine("USER: " + username + " has logged in!");
    }

    static void Main() {
      LAPTOP lap1 = PC;
      lap1("Hydrogen");
    }
  }
}

You can notice that we have instantiated the LAPTOP instance with PC, and invoking it later causes us to print the username passed from the PC method. That is how delegates work.

Method Groups in C#

We sometimes encounter a case where a function may have more than one implementation. It might have another definition with an entirely different parameter set or type or is usually known as an overloaded function.

Syntax:

void print(int x) {
  ///...code
}

void print(int x, int y) {
  ///...code for x and y
}

Such functions or methods are called method groups.

A method group represents a set of methods that have the same name and compatible signatures. This group of methods can be referred to collectively by the delegate, simplifying the invocation of these methods through the delegate instance.

The syntax for referencing a method group involves specifying the method name without parentheses or arguments. For instance:

// Define a delegate
delegate int MyDelegate(int x, int y);

// Methods compatible with the delegate signature
int Add(int a, int b) => a + b;
int Multiply(int a, int b) => a * b;

// Creating a method group
MyDelegate delegateInstance1 = Add;
MyDelegate delegateInstance2 = Multiply;

In this code, Add and Multiply are methods compatible with the MyDelegate delegate signature. The method group creation involves assigning these methods to delegate instances without invoking them, allowing the delegate to reference them.

Method Groups Conversion With Delegate in C#

We’ll look at one of the most common uses of method groups and how to handle them when called. We discussed how method groups are overloaded functions of a basic method, and we can use method groups as we desire by using delegates.

In our example, let’s suppose that we have a method called CAR from which we want to address several other methods, such as DASHBOARD, WHEEL, LIGHTS etc. We will, for convenience, only use two methods; LIGHTS and MIRRORS.

Code Example:

delegate void CAR(bool start);

static void MIRROR(bool start) {
  if (start)
    Console.WriteLine("Mirror is now working!");
  else
    Console.WriteLine("Mirror is not stopped/closed");
}

static void LIGHTS(bool start) {
  if (start)
    Console.WriteLine("Lights are now working!");
  else
    Console.WriteLine("Lights have stopped/closed");
}

The code demonstrates the use of a delegate CAR to reference methods (MIRROR and LIGHTS) with matching signatures. Method groups are created by assigning these methods to delegate instances, allowing for the indirect invocation of methods based on the delegate instances and provided arguments.

This abstraction enables more flexible and dynamic handling of method calls, particularly in scenarios where different methods need to be invoked based on certain conditions or events, as seen with the car-related functionalities of mirrors and lights in this example.

Now that we have defined the methods, let’s implement the MAIN method, where we use delegates to handle these method groups.

CAR car = MIRROR;
car(true);

car = LIGHTS;
car(false);

You can notice the CAR object points to the MIRROR, but later it is made to point to LIGHTS. Then, calling the method will call the function that it points to.

The function’s name is assigned to the car object. Changing the DELEGATE pointer to point at different methods in its group is called METHOD GROUP CONVERSION, and in this scenario, the LIGHTS, MIRRORS, and CAR are all part of the same method group.

Full code:

using System;

namespace ConsoleApplication1 {
  class Program {
    delegate void CAR(bool start);

    static void MIRROR(bool start) {
      if (start)
        Console.WriteLine("Mirror is now working!");
      else
        Console.WriteLine("Mirror is not stopped/closed");
    }

    static void LIGHTS(bool start) {
      if (start)
        Console.WriteLine("Lights are now working!");
      else
        Console.WriteLine("Lights have stopped/closed");
    }

    static void Main() {
      CAR car = MIRROR;
      car(true);

      car = LIGHTS;
      car(false);
    }
  }
}

Output:

Mirror is now working!
Lights have stopped/closed

Simple Custom Delegate for Method Groups in C#

A simple way to create a delegate that can point to method groups is something as simple as follows.

Func<string> f_test = "AA".ToString;
Console.WriteLine(f_test());

Invoking the above will output the string AA as a result. f_test points to the ToString method and calls it.

You can also notice that this function points to the address of the ToString method and not the function itself. That is how pointers work.

Another example has been provided below to understand method groups properly.

Func<string, int> f_test = Convert.ToInt32;
Console.WriteLine(f_test("435"));

Syntax:

Func<param1, param2> name;
// param1 is the parameter passed in the function/method pointed to
// param 2 is the return type of that function/method that the delegate points to

As Convert.ToInt32 has a return type of INT and 18 different overloads. It is imperative to define the parameter type because we want to call f_test with a string "435", and we define param2 as STRING.

Defining param2 is important even if there is just one overload of a function present; because method groups are compiled time constructs, they must be chosen for a single overload. It is important to ensure that param2 contains at least one overload.

You can also remove the cast in LINQ when calling the List.Select(MethodGroup) in a function. We will not discuss LINQ contexts in detail because this article focuses on method groups.

Use Function Pointers for Method Groups in C#

Pointing to different functions from a delegate is only needed if you are working in method groups. You must ensure that the return types and parameters match in such circumstances.

C# already provides a FUNC keyword to allow for pointing to different functions. Just as pointers work by pointing to addresses of objects and variables and then calling them, you can imagine delegates the same way.

Pointers also tend to have the same return type as the objects they address. Delegates also have to take care of parameters due to function requirements.

Pointing to functions is needed when, let’s say, you want to store functions in an array and call them dynamically for your code or pass the functions to other functions to be called. LAMBDA functions serve the same requirement as well.

However, method groups are common clusters with overloaded functions that follow a basic return and parameter type.

Use Cases and Benefits

Method groups offer several advantages and use cases:

  1. Callback Mechanisms: They are widely used in scenarios where callbacks are needed, like event handling or asynchronous programming.
  2. Code Flexibility: Method groups enhance code flexibility by allowing dynamic invocation of methods at runtime, thus enabling more adaptable and modular code structures.
  3. Encapsulation: They aid in encapsulation by allowing a delegate to reference multiple methods, providing a level of abstraction.

Binding Method Groups to Delegates

The binding of a method group to a delegate involves assigning the method group to a delegate instance of a compatible signature. This can be achieved using the delegate keyword or through the use of lambda expressions:

MyDelegate delegateInstance = Add;  // Binding method group using delegate keyword

// Using lambda expression
MyDelegate delegateInstanceLambda = (a, b) => Multiply(a, b);

Delegate Invocation with Method Groups

Once a method group is bound to a delegate, invoking the delegate will call all the methods referenced by the method group. For example:

int result = delegateInstance(2, 3);              // Invokes the Add method
int resultLambda = delegateInstanceLambda(2, 3);  // Invokes the Multiply method

Conclusion

Method groups play a pivotal role in enhancing the flexibility, modularity, and abstraction of C# code. They enable the creation of dynamic and adaptable systems by providing a way to reference multiple methods with compatible signatures through a single delegate instance.

Understanding method groups is essential for leveraging the power of delegates and harnessing the dynamic nature of C# programming, enabling the development of more robust and scalable applications.

Author: Bilal Shahid
Bilal Shahid avatar Bilal Shahid avatar

Hello, I am Bilal, a research enthusiast who tends to break and make code from scratch. I dwell deep into the latest issues faced by the developer community and provide answers and different solutions. Apart from that, I am just another normal developer with a laptop, a mug of coffee, some biscuits and a thick spectacle!

GitHub

Related Article - Csharp Method