Первый аргумент метода PropertyInfo.SetValue - это экземпляр, свойство которого вы хотите установить (или ноль для статических свойств).Вы передаете экземпляр Тип вместо экземпляра CommandContext .Следовательно, вы получаете ошибку.
Однако вам даже не нужно использовать отражение, чтобы установить свойство CommandContext.Message .Вы знаете, что method.DeclaringType имеет тип CommandContext , поэтому вы можете просто уменьшить значение, возвращаемое Activator.CreateInstance :
// ...
if (method != null)
{
var commandContext = (CommandContext)Activator.CreateInstance(method.DeclaringType);
commandContext.Message = rawMessage;
method.Invoke(commandContext, null);
}
// ...
(Я изменил порядок вызова метода и настройки свойства Message , чтобы ваш код имел смысл, иначе Maintenance.SendMessage ничего не печатает.)
Просмотр кода бонуса
Следующая часть должна быть оптимизирована:
var method = Assembly.GetEntryAssembly()
.GetTypes()
.SelectMany(t => t.GetMethods())
.FirstOrDefault(m =>
m.GetCustomAttribute<CommandAttribute>()?.Text == rawMessage.Substring(1).ToLower());
Отражение медленное.Сканирование вашей сборки на наличие отмеченных методов каждый раз, когда вызывается ваш обработчик событий, ухудшает производительность вашего приложения.Метаданные типа не будут меняться во время выполнения приложения, поэтому вы можете легко реализовать какое-либо кэширование здесь:
private delegate void CommandInvoker(Action<CommandContext> configure);
private static CommandInvoker CreateCommandInvoker(MethodInfo method)
{
return cfg =>
{
var commandContext = (CommandContext)Activator.CreateInstance(method.DeclaringType);
cfg(commandContext);
method.Invoke(commandContext, null);
};
}
private static readonly IReadOnlyDictionary<string, CommandInvoker> commandCache = Assembly.GetEntryAssembly()
.GetTypes()
.Where(t => t.IsSubclassOf(typeof(CommandContext)) && !t.IsAbstract && t.GetConstructor(Type.EmptyTypes) != null)
.SelectMany(t => t.GetMethods(), (t, m) => new { Method = m, Attribute = m.GetCustomAttribute<CommandAttribute>() })
.Where(it => it.Attribute != null)
.ToDictionary(it => it.Attribute.Text, it => CreateCommandInvoker(it.Method));
// now MessageReceived becomes as simple as:
private void MessageReceived(object sender, MessageEventArgs e)
{
string rawMessage = "/message";
if (rawMessage.StartsWith('/') && commandCache.TryGetValue(rawMessage.Substring(1), out CommandInvoker invokeCommand))
invokeCommand(ctx => ctx.Message = rawMessage);
}
Вы можете пойти еще дальше и полностью исключить необходимость отражения во время выполнения, используя деревья выражений вместо method.Invoke :
private static CommandInvoker CreateCommandInvoker(MethodInfo method)
{
var configureParam = Expression.Parameter(typeof(Action<CommandContext>));
var commandContextVar = Expression.Variable(method.DeclaringType);
var bodyBlock = Expression.Block(new[] { commandContextVar }, new Expression[]
{
Expression.Assign(commandContextVar, Expression.New(method.DeclaringType)),
Expression.Invoke(configureParam, commandContextVar),
Expression.Call(commandContextVar, method),
});
var lambda = Expression.Lambda<CommandInvoker>(bodyBlock, configureParam);
return lambda.Compile();
}