#c# #reflection #ironpython
#c# #отражение #ironpython
Вопрос:
Я использую IronPython и знаю, как предоставлять методы из моего класса в область действия скрипта:
m_scope.SetVariable("log", new Action<string>(Log));
public void Log(string a)
{
Console.WriteLine(a);
}
Однако вместо SetVariable
того, чтобы каждый раз вызывать, я хочу ускорить процесс с помощью отражения. Итак, я создал атрибут с именем ScriptMethodAttribute
:
public sealed class ScriptMethodAttribute : Attribute
{
public string Name { get; private set; }
public ScriptMethodAttribute(string name)
{
Name = name;
}
}
Таким образом, я могу определить методы внутри моего класса для использования скриптом, например:
[ScriptMethod("log")]
public void Log(string a)
{
Console.WriteLine(a);
}
Теперь я хочу вызвать setVariable для каждого метода, который использует этот атрибут, чтобы ускорить процесс. Однако, похоже, это не работает.
Это служебный метод, который возвращает список Tuple<ScriptMethodAttribute, MethodInfo
.
public static IEnumerable<Tuple<TAttribute, MethodInfo>> FindMethodsByAttribute<TAttribute>()
where TAttribute : Attribute
{
return (from method in AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => !assembly.GlobalAssemblyCache)
.SelectMany(assembly => assembly.GetTypes())
.SelectMany(type => type.GetMethods())
let attribute = Attribute.GetCustomAttribute(method, typeof(TAttribute), false) as TAttribute
where attribute != null
select new Tuple<TAttribute, MethodInfo>(attribute, method));
}
Это находится в конструкторе класса моего скрипта:
foreach (var a in Reflector.FindMethodsByAttribute<ScriptMethodAttribute>())
{
Action action = (Action)Delegate.CreateDelegate(typeof(Action), this, a.Item2);
m_scope.SetVariable(a.Item1.Name, action);
}
Я получаю следующее исключение:
System.ArgumentException: Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type.
Я предполагаю, что это потому, что я должен включить требуемые типы в конструктор действий, но я не знаю, как получить их из MethodInfo
класса.
Ответ №1:
Для обработки методов с одним параметром вы можете использовать этот код:
var parameters = methodInfo.GetParameters().Select(p => p.ParameterType).ToArray();
var delegateType = typeof(Action<>).MakeGenericType(parameters);
var action = methodInfo.CreateDelegate(delegateType, this);
// Now you can call m_scope.SetVariable with action
Все становится сложнее, если вы хотите обрабатывать методы с любым количеством параметров, потому что вам нужно добавить некоторое ветвление в зависимости от количества элементов в parameters
массиве. Если есть два элемента, вам нужно использовать Action<,>
вместо Action<>
, если вам нужно использовать три Action<,,>
, и так далее.
Не очень элегантным, но быстрым способом было бы предварительно выделить массив для каждого типа действий:
private Type[] DelegateTypes = new[]
{
typeof (Action),
typeof (Action<>),
typeof (Action<,>),
typeof (Action<,,>),
typeof (Action<,,,>),
typeof (Action<,,,,>),
typeof (Action<,,,,,>),
typeof (Action<,,,,,,>),
typeof (Action<,,,,,,,>),
typeof (Action<,,,,,,,,>),
typeof (Action<,,,,,,,,,>),
typeof (Action<,,,,,,,,,,>),
typeof (Action<,,,,,,,,,,,>),
typeof (Action<,,,,,,,,,,,,>),
typeof (Action<,,,,,,,,,,,,,>),
typeof (Action<,,,,,,,,,,,,,,>),
typeof (Action<,,,,,,,,,,,,,,,>)
};
Оттуда это просто вопрос доступа к правильному индексу массива, в зависимости от вашего количества параметров:
Type delegateType;
if (parameters.Length == 0)
delegateType = DelegateTypes[0];
else
delegateType = DelegateTypes[parameters.Length].MakeGenericType(parameters);
Комментарии:
1. А что, если мой метод вообще не требует никаких параметров? Тогда как бы я получил тип? Кроме того, есть ли способ получить тип на основе количества параметров, чтобы я мог расширить его до любого количества параметров, которое я хочу, и не ограничивать себя одним?
2. Хорошо, я понял. Это то, о чем я думал. Определенно не элегантно, но очень умно.
3. Когда я пытаюсь добавить действие, которое не принимает параметров, я получаю исключение InvalidOperationException, в котором говорится: System. Действие не является определением GenericTypeDefinition . MakeGenericType может вызываться только для типа, для которого Type . IsGenericTypeDefinition имеет значение true.
4. @GilbertWilliams О, действительно, вам нужен особый случай, когда есть 0 параметров. Отредактировано.
5. Большое вам спасибо! Работает безупречно. Я попытаюсь найти гораздо более простой способ сделать это.