Multicast delegate with double return type, only returns the last method's result, and not All method's results

127 views Asked by At

I am new to this topic and wanted to know is there a way that I could get all method's result and not only the last method's result while using a return type for multicast delegate?

Here is my code:

class Program
    {
        public delegate double rectangleDelegate (double Width, double Heigth);
        class Rectangle
        {
            public double GetPerimeter(double Width, double Heigth)
            {
                return 2 * (Width + Heigth);
            }
            public double GetArea(double Width, double Heigth)
            {
                return Width * Heigth;
            }
        }
        static void Main(string[] args)
        {
            Rectangle rectangle = new Rectangle();

            rectangleDelegate obj = rectangle.GetArea;
            obj += rectangle.GetPerimeter;

            Console.WriteLine(obj(4, 2));

        }
    }

I tried with void for my methods and delegates and printing the results. In this case I could see both methods output on console. But when using a return type like double I only get last methods output.

Can I do something to get each method's return and store it in a variable or a list? Or is it totally wrong approach to expect such a thing from multicast delegates?

3

There are 3 answers

2
DavidG On BEST ANSWER

You can't get all of the return values like this. The only way I know to do it is a little hacky uses GetInvocationList:

rectangleDelegate obj = rectangle.GetArea;
obj += rectangle.GetPerimeter;

foreach(var del in obj.GetInvocationList())
{
    var result = (double)del.DynamicInvoke(4, 2);
    Console.WriteLine(result);
}

Of course, this is a little dangerous as it assumes the delegates of a specific format. You could coerce the delegate to the type you use like this, which means you don't need to use DynamicInvoke:

foreach(rectangleDelegate del in obj.GetInvocationList())
{
    var result = del(4, 2);
    Console.WriteLine(result);
}

Or a safer version:

foreach(var del in obj.GetInvocationList().OfType<rectangleDelegate>())
{
    var result = del(4, 2);
    Console.WriteLine(result);
}
0
JonasH On

I would recommend avoiding the use of multi cast delegates in this way. It is very uncommon to use multi cast delegates outside of events, and I would need to think a fair bit about why someone would write code like that.

If you want to to call multiple delegates and process the result I would recommend using an explicit list/array:

var rectangleDelegateList = new rectangleDelegate[]{ 
    rectangle.GetArea, 
    rectangle.GetPerimeter
}
foreach(var method in rectangleDelegateList ){
    Console.WriteLine(method(4, 2));
}

That should be much easier to read and understand. However, I would prefer if the methods returned Area and Length types respectively, since these should be considered different things.

If you do have an event you would typically pass both input and output in an event argument object:

public class MyEventArg{
    public double Width {get; init;}
    public double Height {get; init;}
    public double Result {get;set;}
}
public event EventHandler<MyEventArgs> myEvent;

private RaiseEvent(){
    var args = new MyEventArgs(){ Width = 4, Height = 2};
    myEvent?.Invoke(this, args);
    Console.WriteLine(args.Result);
}

But this is also a pattern I would be careful with, since you typically do not expect a result value from any event handler. One use case is for things like a closing event, where any event handler is allowed to cancel the closing.

1
Olivier Jacot-Descombes On

A different approach is to change the delegate and to make it add the results to a list:

public delegate void RectangleDelegate(double Width, double Height,
                                       List<double> results);

Without changing the Rectangle class we get:

var rectangle = new Rectangle();
var results = new List<double>();

RectangleDelegate rectDelegate = (w, h, r) => r.Add(rectangle.GetArea(w, h));
rectDelegate += (w, h, r) => r.Add(rectangle.GetPerimeter(w, h));

rectDelegate(4, 2, results);
foreach (double result in results) {
    Console.WriteLine(result);
}

Note that I am using lambda expressions to convert the signature of the rectangle methods into the signature required by the delegate.

Prints

8
12

You could also use a delegate accepting a Rectangle parameter allowing a more flexible usage. We also use a Rectangle2 class having Width and Height properties instead of such parameters in the methods.

public class Rectangle2
{
    public double Width { get; set; }
    public double Height { get; set; }

    public double GetPerimeter()
    {
        return 2 * (Width + Height);
    }

    public double GetArea()
    {
        return Width * Height;
    }
}

public delegate void RectangleDelegate2(Rectangle2 rect, List<double> results);

And call it with:

RectangleDelegate2 rectDelegate = (rect, list) => list.Add(rect.GetArea());
rectDelegate += (rect, list) => list.Add(rect.GetPerimeter());

var results = new List<double>();
rectDelegate(new Rectangle2 { Width = 4, Height = 2 }, results);
rectDelegate(new Rectangle2 { Width = 3, Height = 5 }, results);
foreach (double result in results) {
    Console.WriteLine(result);
}

This prints:

8
12
15
16