【问题标题】:C# Reflection - PropertyInfo.SetValue [Object does not match target type]C# 反射 - PropertyInfo.SetValue [对象与目标类型不匹配]
【发布时间】:2018-08-24 21:11:58
【问题描述】:

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

如果您要在聊天中键入 /message,这将运行条目程序集中的方法,该方法包含文本值为“消息”的 CommandAttribute。所有使用 CommandAttribute 的类都继承自 CommandContext 类。使用反射,我试图设置 CommandContext 属性的值,以便它们可以在包含被调用的命令方法的派生类中使用。

在设置位于 CommandContext 类中的属性值时(在这种情况下为消息),我收到以下错误。

对象与目标类型不匹配

我已尝试其他问题的解决方案,但仍然收到错误消息。 我在下面发布了派生类、基类和方法。请让我知道是否需要任何其他信息来帮助我。谢谢大家的帮助。

此处发生错误:

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);
                }
            }
        }
    }
}

【问题讨论】:

    标签: c# .net reflection


    【解决方案1】:

    PropertyInfo.SetValue 方法的第一个参数是您要设置其属性的实例(或者对于静态属性为 null)。你传入一个 Type 实例而不是 CommandContext 实例。因此你得到了错误。

    但是,您甚至不需要使用反射来设置 CommandContext.Message 属性。您知道 method.DeclaringTypeCommandContext 类型,因此您可以简单地将 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();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-10-06
      • 2017-04-10
      • 1970-01-01
      • 1970-01-01
      • 2018-10-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多