patterncsharpMinor
Mapping a user input string to a corresponding block of code
Viewed 0 times
userblockinputcodemappingstringcorresponding
Problem
My idea is I can define a block of code (inline) that corresponds to an input
I'm used to this sort of structure in JavaScript like so:
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();
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
If we call
Opening to-do list...
Finished command
Opened to-do list
I would recommend instead to change
A couple of other notes:
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 isOpening 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
vcdLookuppublic, exposing it to the world
- You can declare
vcdLookupas anIReadOnlyDictionaryso 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.