C# 获取Func的结果<;对象>;当对象是任务时<;某物>;

C# 获取Func的结果<;对象>;当对象是任务时<;某物>;,c#,delegates,generic-programming,C#,Delegates,Generic Programming,我当前正在使用此代码尝试动态执行已保存的Func: 我不完全清楚您的意图,因为代码不清楚。您正在查找返回类型上的GetAwaiter()方法,但当然除了Task之外,还有其他类型具有此方法。此外,您的代码将错过通过扩展方法等待的东西 如果要假设函数返回一个任务对象(代码当前正在返回),那么只需检查该对象,而不必使用GetAwaiter()方法。相反,您应该动态地调用GetAwaiter()方法,以适应该方法的任何内容 就个人而言,如果不经常调用此代码,我会使用dynamic,尝试调用GetAwa

我当前正在使用此代码尝试动态执行已保存的
Func


我不完全清楚您的意图,因为代码不清楚。您正在查找返回类型上的
GetAwaiter()
方法,但当然除了
Task
之外,还有其他类型具有此方法。此外,您的代码将错过通过扩展方法等待的东西

如果要假设函数返回一个任务对象(代码当前正在返回),那么只需检查该对象,而不必使用
GetAwaiter()
方法。相反,您应该动态地调用
GetAwaiter()
方法,以适应该方法的任何内容

就个人而言,如果不经常调用此代码,我会使用
dynamic
,尝试调用
GetAwaiter()
,捕获失败的异常(因为该方法不存在),然后直接调用委托。如果perf很重要,则可以将类型记忆为等待状态,以便在点击一次异常后跳过该异常。注意,使用
dynamic
,您将适应大多数等待的场景(它仍然找不到扩展方法
GetAwaiter()
s)

以下是一个例子:

private static readonly HashSet_notAwaitable=new HashSet();
公共静态异步任务GetFuncResult(字符串funcName)
{
Func Func=_savedFuncs[funcName];
动态结果=func();
if(!\u notAwaitable.Contains(函数方法))
{
尝试
{
返回等待结果;
}
catch(RuntimeBinderException){}//不可等待
_notAwaitable.Add(函数方法);
}
返回结果;
}
这应该是你想要的,也应该是有效的。
dynamic
运行时支持已经缓存了等待场景的解决方案,并且通过将非等待的
MethodInfo
实例存储在散列集中,代码避免了任何给定委托目标方法多次遭受
RuntimeBinderException

一旦代码“预热”(即在后续过程中以它将被调用的方式调用),它就不应该成为瓶颈

注意,上面的实现假设您没有使用多播委托。考虑到上下文,这似乎是一个合理的假设,因为对于等待的多播委托没有内置的语言支持(或者更确切地说,它可以工作,但是运行时中没有任何东西可以解决等待哪个等待的模糊性)。但是如果您真的需要,您当然可以扩展以上内容以支持多播代理

如果您不关心支持所有可等待的场景,而只支持基于
任务
的场景,那么您可以将上述场景简化为:

公共静态异步任务GetFuncResult(字符串funcName) { Func Func=_savedFuncs[funcName]; 对象结果=func(); 如果(结果是任务) { 等待任务; 返回((动态)任务)。结果; } 返回结果; }
这里,使用
任务
的类型检查代替哈希集。同样地,
动态
运行时支持将为该方法使用的每种类型的任务缓存
结果
访问器,因此一旦预热,其性能将与任何其他面向动态的解决方案一样好

最后,请注意,如果您有一个
Func
,上述操作将不起作用,因为它假定所有
Task
对象都有一个有效的结果。有人可能会说,考虑到歧义性,最好不要在字典中填充这样的内容。但假设该情景值得关注,可以修改上述内容以考虑这种可能性:

公共静态异步任务GetFuncResult(字符串funcName) { Func Func=_savedFuncs[funcName]; 对象结果=func(); 如果(结果是任务) { 类型resultType=result.GetType(); //某些非结果任务场景会返回任务 //对于普通的非泛型任务,请检查两者。 if(resultType!=typeof(任务)&& resultType.GenericTypeArguments[0]。全名!=“System.Threading.Tasks.VoidTaskResult”) { 等待任务; 返回((动态)任务)。结果; } } 返回结果; }
不幸的是,因为在某些情况下编译器使用
Task
而不是非泛型
Task
类型,所以仅检查
Task
是不够的。此外,由于
VoidTaskResult
不是公共类型,因此代码必须检查类型名称是否为
字符串
值,而不是
typeof(Task)
。所以,这有点尴尬。但它将解决以下情况:返回的对象是任务本身,而不是任务的结果


当然,
GetAwaiter()
方法没有这个问题。因此,如果这确实值得关注,那将是选择
GetAwaiter()
方法而不是
is Task
方法的一个原因。

为什么要将对象作为类型放在泛型中?不使用对象作为类型是泛型发明/实现的真正目的。如果不知道这些任务可能有哪些特定的返回值,我们就无法为您提供太多帮助。@Christopher我很感激这个问题。这些函数可以返回任何对象。这本质上是一个合成根,它允许用户注册一个表示对象的函数,对象可以是任何东西。稍后,他们可以根据需要取出该对象。然后按字面意思进行操作
Task GetFuncResult(string funcName)
@Selvin对于这个示例,您没有错。更复杂的真实代码(只需要稍微复杂一点)需要一些用户灵活的签名。大多数都是通用的,比如
GetFun
public async Task<object> GetFuncResult(string funcName) {
    Func<object> func = _savedFuncs[funcName];
    bool isAwaitable = func.Method.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
    if (!isAwaitable) return func();
    else return await ((Func<Task<object>>)func)();
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace TestConsole
{
    class Program
    {
        static Dictionary<string, Func<object>> _savedFuncs;

        static async Task Main(string[] args)
        {
            _savedFuncs = new Dictionary<string, Func<object>>();
            Func<Task<string>> myTask = async () => { return "Test Success"; };
            _savedFuncs.Add("myFunc", myTask);
            Console.WriteLine((await GetFuncResult("myFunc")) ?? "No Value Returned");
            Console.ReadKey();
        }

        public static async Task<object> GetFuncResult(string funcName)
        {
            Func<object> func = _savedFuncs[funcName];
            bool isAwaitable = func.Method.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;
            if (!isAwaitable) return func();
            return await ((Func<Task<object>>)func)();
        }
    }
}