Отражение C # - PropertyInfo.SetValue [Объект не соответствует типу цели] - PullRequest
0 голосов
/ 25 августа 2018

Это система команд, которая работает с использованием атрибутов команд.Пример того, как это работает, приведен ниже.

Если вы введете / message в чате, это запустит метод в сборке записи, содержащей CommandAttribute со значением Text «message».Все классы, которые используют CommandAttribute, наследуются от класса CommandContext.Используя отражение, я пытаюсь установить значение свойств CommandContext, чтобы их можно было использовать в производном классе, который будет содержать вызванный метод Command.

При установке значения свойства, расположенного вКласс CommandContext (Сообщение в данном случае) Я получаю следующую ошибку.

Объект не соответствует типу цели

Я пробовал решения из других вопросов, но явсе еще получаю ошибку.Я разместил класс Derived, базовый класс и метод ниже.Пожалуйста, дайте мне знать, если есть какая-либо другая информация, необходимая, чтобы помочь мне.Спасибо всем за помощь.

Здесь происходит ошибка:

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 голосов
/ 25 августа 2018

Первый аргумент метода 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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...