C#反射-PropertyInfo.SetValue [对象与目标类型不匹配]

时间:2018-08-24 21:11:59

标签: c# .net reflection

这是一个使用命令属性运行的命令系统。下面列出了一个示例。

如果您要在聊天中键入/ 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);
                }
            }
        }
    }
}

1 个答案:

答案 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();
}