https://github.com/ent3m/multicastfunc
MulticastFunc is an alternative to Func delegates that makes retrieving the results of all invocations simple and efficient.
https://github.com/ent3m/multicastfunc
csharp csharp-library delegate dotnet utility utility-library
Last synced: 7 months ago
JSON representation
MulticastFunc is an alternative to Func delegates that makes retrieving the results of all invocations simple and efficient.
- Host: GitHub
- URL: https://github.com/ent3m/multicastfunc
- Owner: ent3m
- License: mit
- Created: 2025-03-06T20:38:23.000Z (about 1 year ago)
- Default Branch: master
- Last Pushed: 2025-08-22T04:45:11.000Z (8 months ago)
- Last Synced: 2025-08-22T06:27:21.105Z (8 months ago)
- Topics: csharp, csharp-library, delegate, dotnet, utility, utility-library
- Language: C#
- Homepage: https://ent3m.github.io/MulticastFunc/
- Size: 77.1 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# Description
MulticastFunc is designed to be an alternative to Func. It provides a simple and efficient way to to retrieve the return values of all invocations instead of only the final invocation.
# The Problem with Func
To retrieve the results of all invocations in a MulticastDelegate, one must get a list of invocations, cast and invoke each delegate individually, and store the results in an array.
The code looks like this with LINQ:
```csharp
T[]? results = myDelegate?.GetInvocationList().Cast>().Select(f => f.Invoke()).ToArray();
```
You can create an extension method to avoid typing that everytime. However, the allocation cost of `GetInvocationList` *cannot* be avoided. Invoking a `Func` or other MulticastDelegates this way is slow and generates a lot of garbage over time.
`MulticastFunc` solves this problem by having `Invoke` return an array of results without compromising on [performance](#Benchmarks). In addition, it behaves similarly to a delegate with its immutability and add/remove syntax, making it a suitable replacement for any `Func` delegate.
# Usage
Adding and removing:
```csharp
multicastFunc += MyMethod;
multicastFunc += () => "Hello World";
multicastFunc -= MyMethod;
multicastFunc -= MyFunc;
```
Invoking:
```csharp
T[]? results = multicastFunc?.Invoke();
```
Use as a backing field for events:
```csharp
public event Func EventHappened
{
add => multicastFunc += value;
remove => multicastFunc -= value;
}
private MulticastFunc? multicastFunc = default;
```
Get delegate count:
```csharp
int count = multicastFunc.Count;
```
Store the results in a buffer:
```csharp
Task[] buffer = ArrayPool.Shared.Rent(multicastFunc.Count);
int written = multicastFunc.Invoke(buffer);
// or
ReadOnlySpan tasks = multicastFunc.Invoke(buffer.AsSpan());
```
# Installation
Install with [nuget](https://www.nuget.org/packages/MulticastFunc/) or download and copy the project into your solution.
# Technical Details
MulticastFunc maintains a `Delegate[]` that is updated whenever a function is added or removed. Invoking consists of iterating over the delegate, invoking each function, and storing the results, bypassing `GetInvocationList`. This makes the invocation process fast and garbage-free, especially if the overload that accepts a buffer is used.
It overloads the `+` and `-` operators to imitate the add/remove behavior of a delegate. Similar to a delegate, it is immutable, meaning invoking is thread-safe. The trade off is that adding and removing is slower with a MulticastFunc than with a delegate.
Another limitation is that, because the compiler does not recognize MulticastFunc as a delegate type, you cannot assign a method group to it directly. This means that `multicastFunc = MyMethod` is not allowed, but `multicastFunc += MyMethod` is allowed.
# Benchmarks
The performance of `MulticastFunc.Invoke()` is roughly equals to that of `Func.Invoke()` and is *~6 times* faster than invoking with LINQ.
| Method | DelegateCount | Mean | Ratio | Allocated | Alloc Ratio |
|-------------------------------- |-------------- |-------------:|------:|----------:|------------:|
| Invoke_Func_Linq | 25 | 332.711 ns | 6.17 | 792 B | 6.19 |
| Invoke_Func | 25 | 55.636 ns | 1.03 | - | 0.00 |
| Invoke_MulticastFunc | 25 | 53.958 ns | 1.00 | 128 B | 1.00 |
| Invoke_MulticastFunc_SpanBuffer | 25 | 39.281 ns | 0.73 | - | 0.00 |