Skip to content

BHoM_Engine: RunExtensionMethodAsync and TryRunExtensionMethodAsync added #3191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions BHoM_Engine/Compute/RunExtensionMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.oM.Base;
using BH.oM.Base.Attributes;
using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

namespace BH.Engine.Base
{
Expand Down Expand Up @@ -65,6 +63,39 @@ public static object RunExtensionMethod(object target, string methodName, object
}

/***************************************************/

[Description("Asynchronously runs an extension method accepting a single argument based on a provided object and method name.\n" +
"Finds the method via reflection the first time it is run, then compiles it to a function and stores it for subsequent calls.")]
[Input("target", "The object to find and run the extension method for.")]
[Input("methodName", "The name of the method to be run.")]
[Output("result", "The result of the method execution. If no method was found, null is returned.")]
public static async Task<object> RunExtensionMethodAsync(object target, string methodName)
{
Output<bool, object> result = await TryRunExtensionMethodAsync(target, methodName);
if (result.Item1)
return result.Item2;
else
return null;
}

/***************************************************/

[Description("Asynchronously runs an extension method accepting multiple arguments based on a provided main object and method name and additional arguments.\n" +
"Finds the method via reflection the first time it is run, then compiles it to a function and stores it for subsequent calls.")]
[Input("target", "The first of the argument of the method to find and run the extention method for.")]
[Input("methodName", "The name of the method to be run.")]
[Input("parameters", "The additional arguments of the call to the method, skipping the first argument provided by 'target'.")]
[Output("result", "The result of the method execution. If no method was found, null is returned.")]
public static async Task<object> RunExtensionMethodAsync(object target, string methodName, object[] parameters)
{
Output<bool, object> result = await TryRunExtensionMethodAsync(target, methodName, parameters);
if (result.Item1)
return result.Item2;
else
return null;
}

/***************************************************/
}
}

Expand Down
118 changes: 86 additions & 32 deletions BHoM_Engine/Compute/TryRunExtensionMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,15 @@
* along with this code. If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.
*/

using BH.oM.Base.Debugging;
using BH.oM.Base;
using BH.oM.Base.Attributes;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
using BH.oM.Base.Attributes;
using System.Collections.Concurrent;

namespace BH.Engine.Base
{
Expand Down Expand Up @@ -69,22 +65,99 @@ public static bool TryRunExtensionMethod(this object obj, string methodName, obj
return TryRunExtensionMethod(methodName, new object[] { obj }.Concat(parameters).ToArray(), out result);
}

/***************************************************/

[Description("Looks for an extension method applicable to the input object with the provided `methodName` and, if found, invokes it asynchronously.\n" +
"Extension methods are searched using Reflection through all BHoM assemblies.\n" +
"If no method is found, this returns `false`, and the `result` is null.")]
[Input("obj", "Object whose extension method is to be found, and to which the method will be applied in order to obtain the result.")]
[Input("methodName", "Name of the extension method defined for the input object that is to be found in any of the BHoM assemblies.")]
[Output("First output: true if a method was found and an invocation was attempted. False otherwise." +
"\nSecond output: result of the call if an attempt was made.")]
public static async Task<Output<bool, object>> TryRunExtensionMethodAsync(this object obj, string methodName)
{
return await TryRunExtensionMethodAsync(methodName, new object[] { obj });
}

/***************************************************/

[Description("Looks for an extension method applicable to the input object with the provided `methodName` and and, if found, invokes it asynchronously.\n" +
"Extension methods are searched using Reflection through all BHoM assemblies.\n" +
"If no method is found, this returns `false`, and the `result` is null.")]
[Input("obj", "Object whose extension method is to be found, and to which the method will be applied in order to obtain the result.")]
[Input("methodName", "Name of the extension method defined for the input object that is to be found in any of the BHoM assemblies.")]
[Input("parameters", "The additional arguments of the call to the method, skipping the first argument provided by 'target'.")]
[Output("First output: true if a method was found and an invocation was attempted. False otherwise." +
"\nSecond output: result of the call if an attempt was made.")]
public static async Task<Output<bool, object>> TryRunExtensionMethodAsync(this object obj, string methodName, object[] parameters)
{
return await TryRunExtensionMethodAsync(methodName, new object[] { obj }.Concat(parameters).ToArray());
}

/***************************************************/
/**** Private Methods ****/
/***************************************************/

[Description("Runs the requested method and returns the result. For performance reasons compiles the method to a function the first time it is run, then stores it for subsequent calls.")]
private static bool TryRunExtensionMethod(string methodName, object[] parameters, out object result)
{
if (parameters == null || parameters.Length == 0 || parameters.Any(x => x == null) || string.IsNullOrWhiteSpace(methodName))
Func<object[], object> func = ExtensionMethodToRun(methodName, parameters);

// Try calling the method
try
{
if (func == null)
{
result = null;
return false;
}
else
{
result = func(parameters);
return true;
}
}
catch (Exception e)
{
BH.Engine.Base.Compute.RecordError($"Failed to run {methodName} extension method.\nError: {e.Message}");
result = null;
return false;
}
}

/***************************************************/

[Description("Asynchronously runs the requested method and returns the result. For performance reasons compiles the method to a function the first time it is run, then stores it for subsequent calls.")]
private static async Task<Output<bool, object>> TryRunExtensionMethodAsync(string methodName, object[] parameters)
{
Func<object[], object> func = ExtensionMethodToRun(methodName, parameters) as Func<object[], object>;

// Try calling the method
try
{
if (func == null)
return new Output<bool, object> { Item1 = false, Item2 = null };
else
return new Output<bool, object> { Item1 = true, Item2 = await (func(parameters) as dynamic) };
}
catch (Exception e)
{
BH.Engine.Base.Compute.RecordError($"Failed to run {methodName} extension method.\nError: {e.Message}");
return new Output<bool, object> { Item1 = false, Item2 = null };
}
}

/***************************************************/

[Description("Finds an extension method with given name and parameters. For performance reasons compiles the method to a function the first time it is run, then stores it for subsequent calls.")]
private static Func<object[], object> ExtensionMethodToRun(string methodName, object[] parameters)
{
if (parameters == null || parameters.Length == 0 || parameters.Any(x => x == null) || string.IsNullOrWhiteSpace(methodName))
return null;

//Construct key used to store/extract method
string key = methodName + string.Join("", parameters.Select(x => x.GetType().ToString()));

// If the method has been called before, use the previously compiled function
Func<object[], object> func;
if (FunctionPreviouslyCompiled(key))
Expand All @@ -110,7 +183,7 @@ private static bool TryRunExtensionMethod(string methodName, object[] parameters
{
//Check that the parameter has a default value.
//This should always be true, based on the logic of the ExtensionMethodToCall but additional check here for saftey
if (parameterInfo[i].HasDefaultValue)
if (parameterInfo[i].HasDefaultValue)
{
defaultArgs.Add(parameterInfo[i].DefaultValue); //Add the argument to be used when calling the function
}
Expand All @@ -128,30 +201,11 @@ private static bool TryRunExtensionMethod(string methodName, object[] parameters
else
func = methodFunc; //Provided parameter count matches the parameters of the method -> set func to the compiled function of the method
}

StoreCompiledFunction(key, func);
}

// Try calling the method
try
{
if (func == null)
{
result = null;
return false;
}
else
{
result = func(parameters);
return true;
}
}
catch (Exception e)
{
BH.Engine.Base.Compute.RecordError($"Failed to run {methodName} extension method.\nError: {e.Message}");
result = null;
return false;
}
return func;
}

/***************************************************/
Expand Down