这是一个使用命令属性运行的命令系统。下面列出了一个示例。
如果您要在聊天中键入/ message,它将在条目程序集中运行该方法,该程序集包含一个CommandAttribute,其Text值为“ message”。所有使用CommandAttribute的类都从CommandContext类继承。使用反射,我试图设置CommandContext属性的值,以便可以在包含调用的Command方法的派生类中使用它们。
设置CommandContext类中的属性的值(在这种情况下为Message)时,出现以下错误。
对象与目标类型不匹配
我已经尝试了其他问题的解决方案,但仍然收到错误消息。 我在下面发布了派生类,基类和方法。请让我知道是否还有其他需要帮助的信息。谢谢大家的帮助。
此处发生错误:
messageProp.SetValue(baseType, Convert.ChangeType(rawMessage, messageProp.PropertyType), null);
命令属性
namespace RocketNET.Attributes
{
public class CommandAttribute : Attribute
{
public string Text { get; private set; }
public CommandAttribute(string text)
{
Text = text;
}
}
}
基本类
namespace RocketNET
{
public class CommandContext
{
public string Message { get; internal set; }
public CommandContext() { }
}
}
派生类
namespace ACGRocketBot.Commands
{
public class Maintenance : CommandContext
{
[Command("message")]
public void SendMessage()
{
Console.WriteLine(Message);
}
}
}
方法
namespace RocketNET
{
public class RocketClient
{
private void MessageReceived(object sender, MessageEventArgs e)
{
string rawMessage = "/message";
if (rawMessage[0] == _commandPrefix)
{
var method = Assembly.GetEntryAssembly()
.GetTypes()
.SelectMany(t => t.GetMethods())
.FirstOrDefault(m =>
m.GetCustomAttribute<CommandAttribute>()?.Text == rawMessage.Substring(1).ToLower());
if (method != null)
{
method.Invoke(Activator.CreateInstance(method.DeclaringType), null);
var baseType = method.DeclaringType.BaseType;
var messageProp = baseType.GetProperty("Message");
messageProp.SetValue(baseType, Convert.ChangeType(rawMessage, messageProp.PropertyType), null);
}
}
}
}
}
答案 0 :(得分:2)
PropertyInfo.SetValue方法的第一个参数是您要设置其属性的实例(对于静态属性,则为null)。您传入 Type 的实例,而不是 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);
}
使用expression trees代替 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();
}