HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Mapping a user input string to a corresponding block of code

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
userblockinputcodemappingstringcorresponding

Problem

My idea is I can define a block of code (inline) that corresponds to an input string then can call that block of code when given that input.

I'm used to this sort of structure in JavaScript like so:

var lookup = {
  "foo": function() { ... },
  "bar": function() { ... },
};

lookup["foo"];


And I'm looking for a C# equivalent. So far I've come up with the following and it seems to work, but I don't know if it could be simplified or improved upon.

```
class CortanaFunctions
{
/*
This is the lookup of VCD CommandNames as defined in
CustomVoiceCommandDefinitios.xml to their corresponding actions
*/
public readonly static Dictionary vcdLookup = new Dictionary{

/*
{, (Action)(async () => {

})}
*/

{"OpenToDoList", (Action)(async () => {
StorageFile file = await Package.Current.InstalledLocation.GetFileAsync(@"ToDo.doc");
await Launcher.LaunchFileAsync(file);
})},

{"OpenReddit", (Action)(async () => {
Uri website = new Uri(@"http://www.reddit.com");
await Launcher.LaunchUriAsync(website);
})},

};

/*
Register Custom Cortana Commands from VCD file
*/
public static async void RegisterVCD()
{
StorageFile vcd = await Package.Current.InstalledLocation.GetFileAsync(
@"CustomVoiceCommandDefinitions.xml");
await VoiceCommandDefinitionManager
.InstallCommandDefinitionsFromStorageFileAsync(vcd);
}

/*
Look up the spoken command and execute its corresponding action
*/
public static void RunCommand(VoiceCommandActivatedEventArgs cmd)
{
SpeechRecognitionResult result = cmd.Result;
string commandName = result.RulePath[0];
vcdLookup[commandName].DynamicInvoke();

Solution

A problem with the code is that the async lambdas in the dictionary are actually async void.


Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started. -- Async/Await - Best Practices in Asynchronous Programming

It's also confusing because vcdLookup[commandName].DynamicInvoke() looks like a blocking call, but it isn't. Consider this code:

private static readonly Dictionary vcdLookup = new Dictionary
{
    { "OpenToDoList", (Action)(async () =>
        {
            Console.WriteLine("Opening to-do list...");
            await Task.Delay(TimeSpan.FromSeconds(1));
            Console.WriteLine("Opened to-do list");
        })
    }
};

public static void RunCommand(string command)
{
    vcdLookup[command].DynamicInvoke();
    Console.WriteLine("Finished command");
}


If we call RunCommand("OpenToDoList"), the output is


Opening to-do list...


Finished command


Opened to-do list

I would recommend instead to change vcdLookup to a Dictionary>.

private static readonly IReadOnlyDictionary> vcdLookup = new Dictionary>
{
    { "OpenToDoList", async () =>
        {
            StorageFile file = await Package.Current.InstalledLocation.GetFileAsync(@"ToDo.doc");
            await Launcher.LaunchFileAsync(file);
        }
    }
};

public static async Task RunCommandAsync(string command)
{
    SpeechRecognitionResult result = command.Result;
    string commandName = result.RulePath[0];
    await vcdLookup[commandName]();
}


A couple of other notes:

  • Async methods should have the suffix Async, by convention



  • You probably don't want to make vcdLookup public, exposing it to the world



  • You can declare vcdLookup as an IReadOnlyDictionary so that its contents are not modified after creation

Code Snippets

private static readonly Dictionary<string, Delegate> vcdLookup = new Dictionary<string, Delegate>
{
    { "OpenToDoList", (Action)(async () =>
        {
            Console.WriteLine("Opening to-do list...");
            await Task.Delay(TimeSpan.FromSeconds(1));
            Console.WriteLine("Opened to-do list");
        })
    }
};

public static void RunCommand(string command)
{
    vcdLookup[command].DynamicInvoke();
    Console.WriteLine("Finished command");
}
private static readonly IReadOnlyDictionary<string, Func<Task>> vcdLookup = new Dictionary<string, Func<Task>>
{
    { "OpenToDoList", async () =>
        {
            StorageFile file = await Package.Current.InstalledLocation.GetFileAsync(@"ToDo.doc");
            await Launcher.LaunchFileAsync(file);
        }
    }
};

public static async Task RunCommandAsync(string command)
{
    SpeechRecognitionResult result = command.Result;
    string commandName = result.RulePath[0];
    await vcdLookup[commandName]();
}

Context

StackExchange Code Review Q#112252, answer score: 2

Revisions (0)

No revisions yet.