Подтвердить что ты не робот

Как я могу определить, является ли метод С# асинхронным/ожидающим через отражение?

например.

class Foo { public async Task Bar() { await Task.Delay(500); } }

Если мы размышляем над этим классом и методом, как я могу определить, является ли это реальным методом async/await, а не просто методом, который возвращает задачу?

class Foo { public Task Bar() { return Task.Delay(500); } }
4b9b3361

Ответ 1

В моей копии вашего кода MethodInfo для метода async содержит следующие элементы в свойстве CustomAttributes:

  • DebuggerStepThroughAttribute
  • AsyncStateMachineAttribute

тогда как MethodInfo для обычного метода не содержит элементов в своем свойстве CustomAttributes.

Похоже, что AsyncStateMachineAttribute должен быть надежно найден методом async, а не стандартным.

Изменение: На самом деле эта страница даже содержит следующее в примерах!

Как показано в следующем примере, вы можете определить, помечен ли метод модификатором Async (Visual Basic) или async (С# Reference). В этом примере IsAsyncMethod выполняет следующие шаги:

  • Получает объект MethodInfo для имени метода с помощью Type.GetMethod.

  • Получает объект Type для атрибута с помощью оператора GetType (Visual Basic) или typeof (справочник по С#).

  • Получает объект атрибута для метода и типа атрибута с помощью MethodInfo.GetCustomAttribute. Если GetCustomAttribute возвращает Nothing (Visual Basic) или null (С#), метод не содержит атрибут.

private static bool IsAsyncMethod(Type classType, string methodName)
{
    // Obtain the method with the specified name.
    MethodInfo method = classType.GetMethod(methodName);

    Type attType = typeof(AsyncStateMachineAttribute);

    // Obtain the custom attribute for the method. 
    // The value returned contains the StateMachineType property. 
    // Null is returned if the attribute isn't present for the method. 
    var attrib = (AsyncStateMachineAttribute)method.GetCustomAttribute(attType);

    return (attrib != null);
}

Ответ 2

Damien_The_Unbeliever бросил интересный вызов. Я думаю, что проверка на AsyncStateMachineAttribute не является достаточным решением. Первоначальный вопрос не должен заключаться в том, является ли метод асинхронным. Вместо этого должно быть ли это ожидаемым. Оба примера метода в ответе Дэмьена вернут true, если вы проверите метод GetAwaiter() в типе возврата. Однако только метод, помеченный async, будет включать AsyncStateMachineAttribute в коллекцию пользовательских атрибутов.

Знание того, является ли метод ожидаемым, важно, если вы хотите использовать MethodInfo.Invoke() для вызова метода, и вы не знаете заранее, ожидают ли методы, которые могут быть зарегистрированы, например, в посреднике сообщений.

var isAwaitable = _methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;

object result = null;
if (isAwaitable)
{
    result = await (dynamic)_methodInfo.Invoke(_instance, _parameterArray);
}
else
{
    result = _methodInfo.Invoke(_instance, _parameterArray);
}

EDIT: Хорошая идея проверить тип возвращаемого значения на MethodInfo. Это мой пересмотренный код.

var isAwaitable = _methodInfo.ReturnType.GetMethod(nameof(Task.GetAwaiter)) != null;

object invokeResult = null;
if (isAwaitable)
{
    if (_methodInfo.ReturnType.IsGenericType)
    {
        invokeResult = (object)await (dynamic)_methodInfo.Invoke(_instance, arguments);
    }
    else
    {
        await (Task)_methodInfo.Invoke(_instance, arguments);
    }
}
else
{
    if (_methodInfo.ReturnType == typeof(void))
    {
        _methodInfo.Invoke(_instance, arguments);
    }
    else
    {
        invokeResult = _methodInfo.Invoke(_instance, arguments);
    }
}

Ответ 3

Вот пример двух методов, и я спрашиваю вас, почему вы думаете, что с ними нужно обращаться иначе:

    public static async Task<int> M1(int value)
    {
        await Task.Delay(20000);
        return value;
    }

    public static Task<int> M2(int value)
    {
        return Task.Delay(20000).ContinueWith<int>(_=>value);
    }

Оба они имеют в ручном режиме то же самое поведение во время выполнения - за 20 секунд они ничего не делают (и не удерживают нить в течение этого периода), а затем они запускают небольшой делегат, который просто передает значение который был первоначально передан методу.

И все-таки вы будете относиться к одному совершенно по-другому от другого, потому что я предпочитаю использовать компилятор, чтобы скрыть некоторые из сантехники?