简体   繁体   中英

C# Reflection - PropertyInfo.SetValue [Object does not match target type]

This is a command system that runs using Command Attributes. An example of how this works is listed below.

If you were to type /message in the chat, this will run the method in the entry assembly that contains a CommandAttribute with the Text value of "message". All Classes that use the CommandAttribute inherit from the CommandContext Class. Using reflection, I am trying to set the value of the CommandContext properties so that they can be used in the Derived Class which would contain the Command Method that was invoked.

When setting the value of a property located in the CommandContext Class (Message in this case) I am receiving the following error.

Object does not match target type

I have tried the solutions from other questions but I am still receiving the error. I have posted the Derived class, Base Class, and the Method below. Please let me know if there is any other information needed to help me out. Thank you all for your help.

Error is occurring here:

messageProp.SetValue(baseType, Convert.ChangeType(rawMessage, messageProp.PropertyType), null);

COMMAND ATTRIBUTE

namespace RocketNET.Attributes
{
    public class CommandAttribute : Attribute
    {
        public string Text { get; private set; }
        public CommandAttribute(string text)
        {
            Text = text;
        }
    }
}

BASE CLASS

namespace RocketNET
{
    public class CommandContext
    {
        public string Message { get; internal set; }

        public CommandContext() { }
    }
}

DERIVED CLASS

namespace ACGRocketBot.Commands
{
    public class Maintenance : CommandContext
    {
        [Command("message")]
        public void SendMessage()
        {
            Console.WriteLine(Message);
        }
    }
}

METHOD

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

The first argument of the PropertyInfo.SetValue method is the instance whose property you want to set (or null for static properties). You pass in an instance of Type instead of an instance of CommandContext . Hence you get the error.

However, you don't even need to use reflection to set the CommandContext.Message property. You know that method.DeclaringType is of type CommandContext so you can simply downcast the object returned by Activator.CreateInstance :

// ...
if (method != null)
{
    var commandContext = (CommandContext)Activator.CreateInstance(method.DeclaringType);
    commandContext.Message = rawMessage;
    method.Invoke(commandContext, null);
}
// ...

(I reversed the order of the method invocation and the setting of Message property so that your code makes sense, otherwise Maintenance.SendMessage wouldn't print anything.)

Bonus code review

The following part should be optimized:

var method = Assembly.GetEntryAssembly()
    .GetTypes()
    .SelectMany(t => t.GetMethods())
    .FirstOrDefault(m =>
        m.GetCustomAttribute<CommandAttribute>()?.Text == rawMessage.Substring(1).ToLower());

Reflection is slow. Scanning your assembly for marked methods each time your event handler called will degrade the performance of your application. Type metadata won't change during the run of your application so you can easily implement some kind of caching here:

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

You can go even further and completely eliminate the need of reflection during execution by using expression trees instead of 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();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM