I have the following code, which stores a list of actions to perform based on the type of value passed as an argument...
Action<int> intAction = x => Console.WriteLine($"You sent an int: {x}");
Action<bool> boolAction = x => Console.WriteLine($"You sent a bool: {x}");
var funcsByInputType = new Dictionary<Type, Action<object>>();
funcsByInputType[typeof(int)] = (object x) => intAction((int)x);
funcsByInputType[typeof(bool)] = (object x) => boolAction((bool)x);
PerformAction(true);
PerformAction(42);
void PerformAction<TValue>(TValue value)
{
if (funcsByInputType!.TryGetValue(typeof(TValue), out Action<object>? action))
{
action(value!);
}
}
This works as expected.
What I now want to do is create the dictionary from a single method that I only call once, so I can rewrite my code like this instead.
Action<int> intAction = x => Console.WriteLine($"You sent an int: {x}");
Action<bool> boolAction = x => Console.WriteLine($"You sent a bool: {x}");
funcsByInputType = MakeFuncsByInputType(intAction, boolAction);
PerformAction(true);
PerformAction(42);
void PerformAction<TValue>(TValue value)
{
if (funcsByInputType!.TryGetValue(typeof(TValue), out Action<object>? action))
{
action(value!);
}
}
How could I write the MakeFuncsByInputType
method?
Note that I don't want to make multiple calls to the method, and I don't want to have my parameters defined as params object[]
CodePudding user response:
This is not possible, because via an Action<object>
as an alias of Action<string>
I would be able to pass 42
instead of a string.
So I had to go with a builder pattern instead.
public static partial class Reducer { public static Builder New() => new Builder();
public class Builder<TState>
{
private bool Built;
private ImmutableArray<KeyValuePair<Type, Func<TState, object, Result<TState>>>> TypesAndReducers;
internal Builder() => TypesAndReducers = ImmutableArray.Create<KeyValuePair<Type, Func<TState, object, Result<TState>>>>();
public Builder<TState> Add<TDelta>(Func<TState, TDelta, Result<TState>> reducer)
{
if (reducer is null)
throw new ArgumentNullException(nameof(reducer));
EnsureNotBuilt();
TypesAndReducers = TypesAndReducers.Add(new(typeof(TDelta), (state, delta) => reducer(state, (TDelta)delta)));
return this;
}
public Func<TState, object, Result<TState>> Build()
{
EnsureNotBuilt();
if (TypesAndReducers.Length == 0)
throw new InvalidOperationException("Must add at least one reducer to build.");
Built = true;
var dictionary = TypesAndReducers
.GroupBy(x => x.Key)
.ToDictionary(x => x.Key, x => x.Select(x => x.Value));
return (TState state, object delta) =>
{
if (delta is null)
throw new ArgumentNullException(nameof(delta));
if (!dictionary.TryGetValue(delta.GetType(), out var reducers))
return (false, state);
bool anyChanged = false;
TState newState = state;
foreach(var reducer in reducers)
{
(bool changed, newState) = reducer(newState, delta);
anyChanged |= changed;
}
return anyChanged
? (true, newState)
: (false, state);
};
}
private void EnsureNotBuilt()
{
if (Built)
throw new InvalidOperationException("Reducer has already been built.");
}
}
}