pizycki pizycki - 4 months ago 8
C# Question

How can I convert set of if conditions with async method calls to Dictionary?

I've got some method calls looking like this

if (SectionContainedWithin(args, RegisterSection.StudentPersonalData))
schoolRegister.StudentPersonalData = await _sectionGeneratorsProvider.StudentPersonalDataGenerator.GenerateAsync(args);

if (SectionContainedWithin(args, RegisterSection.StudentAttendances))
schoolRegister.StudentAttendances = await _sectionGeneratorsProvider.StudentMonthAttendancesGenerator.GenerateAsync(args);

if (SectionContainedWithin(args, RegisterSection.Grades))
schoolRegister.Grades = await _sectionGeneratorsProvider.GradesGenerator.GenerateAsync(args);

// More generating here ...


Every
GenerateAsync
produces object of different type.

public interface IGenerator<TResult, in TArgs>
{
Task<TResult> GenerateAsync(TArgs args);
}


How can I rewrite those
if
s so I could define a list of actions and conditions and then iterate through them.
Something like:

var sections = new Dictionary<bool, Func<Task>>()
{
{
SectionContainedWithin(args, RegisterSection.StudentPersonalData),
() => schoolRegister.StudentPersonalData = await _sectionGeneratorsProvider.StudentPersonalDataGenerator.GenerateAsync(args);
},

// More generating here ...
}

foreach(var item in sections)
{
if(item.Key)
{
await item.Value();
}
}


SOLUTION:

Thanks to @peter-duniho answer I dropped idea of creating
Dictionary<bool, Func<Task>>
and replaced it with
IReadOnlyDictionary<RegisterSection, Func<RegisterXml, RegisterGenerationArgs, Task>>
since it makes more sense.
Multiple keys, not only two true/false.

So thats what I ended up with

private IReadOnlyDictionary<RegisterSection, Func<RegisterXml, RegisterGenerationArgs, Task>> CreateSectionActionsDictionary()
{
return new Dictionary<RegisterSection, Func<RegisterXml, RegisterGenerationArgs, Task>>
{
{ RegisterSection.RegisterCover, async(reg, args) => reg.Cover = await _sectionGenerators.RegisterCoverGenerator.GenerateAsync(args) },
{ RegisterSection.StudentPersonalData, async(reg, args) => reg.StudentPersonalData = await _sectionGenerators.StudentPersonalDataGenerator.GenerateAsync(args)},

// Add more generating here ...
};
}

private async Task GenerateSectionsAsync(RegisterGenerationArgs args, RegisterXml reg)
{
foreach (var sectionAction in SectionActions)
if (SectionContainedWithin(args, sectionAction.Key))
await sectionAction.Value(reg, args);

}

Answer

If I understand the code example correctly, a dictionary really isn't the right tool for this job. You appear to be using it only to store pairs of values; you're not using the primary feature of a dictionary, which is to be able to map a key value you already know to another value.

In addition, the code example you are proposing won't work because you need the args value in order to evaluated the SectionContainedWithin() call. Even if you were trying to declare the dictionary in a context where args is valid and can be used to initialize the dictionary, you would have the problem that it makes the key type bool, which would mean you could only have two entries in the dictionary at most, and would have no way to actually handle all of the combinations where the SectionContainedWithin() method returned true.

Without a good Minimal, Complete, and Verifiable code example that shows clearly what you're doing, it's impossible to know for sure what exactly you need. But it will look something like this:

struct SectionGenerator<TArgs>
{
    public readonly RegisterSection RegisterSection;
    public readonly Func<TArgs, Task> Generate;

    public SectionGenerator(RegisterSection registerSection, Func<TArgs, Task> generate)
    {
        RegisterSection = registerSection;
        Generate = generate;
    }
}

SectionGenerator<TArgs>[] generators =
{
    new SectionGenerator<TArgs>(RegisterSection.StudentPersonalData,
        async args => schoolRegister.StudentPersonalData = await _sectionGeneratorsProvider.StudentPersonalDataGenerator.GenerateAsync(args);
    // etc.
}

Then you can do something like:

foreach (SectionGenerator<TArgs> generator in generators)
{
    if (SectionContainedWithin(args, generator.RegisterSection))
    {
        await generator.Generate(args);
    }
}

Assuming it's reasonable for all these async operations to be in progress concurrently, you could even do something like this:

await Task.WhenAll(generators
    .Where(g => SectionContainedWithin(args, g.RegisterSection))
    .Select(g => g.Generate(args));