Получить экземпляр класса, который наследуется от абстрактного класса и имеет конкретное значение в свойстве - PullRequest
0 голосов
/ 05 мая 2018

Я работаю на фабрике, чтобы создать конкретные экземпляры на основе двух критериев:

1) Класс должен наследоваться от определенного абстрактного класса

2) Класс должен иметь определенное значение в переопределенном свойстве

Мой код выглядит так:

public abstract class CommandBase
{
    public abstract string Prefix { get; }
}

public class PaintCommand : CommandBase
{
    public override string Prefix { get; } = "P";
}

public class WalkCommand : CommandBase
{
    public override string Prefix { get; } = "W";
}

class Program
{
    static void Main(string[] args)
    {
        var paintCommand = GetInstance("P");
        var walkCommand = GetInstance("W");  

        Console.ReadKey();
    }

    static CommandBase GetInstance(string prefix)
    {
        try
        {            
            var currentAssembly = Assembly.GetExecutingAssembly();
            var concreteType = currentAssembly.GetTypes().Where(t => t.IsSubclassOf(typeof(CommandBase)) &&
                                                                     !t.IsAbstract &&
                                                                     t.GetProperty("Prefix").GetValue(t).ToString() == prefix).SingleOrDefault();

            if (concreteType == null)
                throw new InvalidCastException($"No concrete type found for command: {prefix}");

            return (CommandBase)Activator.CreateInstance(concreteType);
        }
        catch (Exception ex)
        {            
            return default(CommandBase);
        }
    }
}

Я получаю ошибку:

{System.Reflection.TargetException: объект не соответствует типу цели. в System.Reflection.RuntimeMethodInfo.CheckConsistency (Object target) в System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck (Object obj, BindingFlags invokeAttr, связыватель Binder, параметры Object [], CultureInfo culture) в System.Reflectionvo.InfoMoneIn SystemIn.jpg BindingFlags invokeAttr, связыватель Binder, параметры Object [], культура CultureInfo)

Ответы [ 2 ]

0 голосов
/ 06 мая 2018

Как отметил спонсор в своем комментарии, причиной этой конкретной ошибки является следующая строка:

t.GetProperty("Prefix").GetValue(t)

Здесь t - это переменная Type, содержащая класс, например WalkCommand. Вы получаете объект PropertyInfo для свойства Prefix этого класса, а затем пытаетесь использовать GetValue() для чтения значения этого свойства из экземпляра объекта WalkCommand.

Проблема в том, что вы не передаете GetValue() экземпляр класса WalkCommand, вы передаете ему Type, поэтому Reflection затем вызывает это исключение.

Есть несколько способов справиться с этим:

1) Создайте экземпляр каждого типа на лету, просто чтобы прочитать его префикс (я действительно не рекомендовал бы делать это):

var instance = currentAssembly.GetTypes()
    .Where(t => t.IsSubclassOf(typeof(CommandBase)) && !t.IsAbstract)
    .Select(t => new { t, i = (CommandBase)Activator.CreateInstance(t) })
    .Where(x => x.t.GetProperty("Prefix").GetValue(x.i).ToString() == prefix)
    .Select(x => x.i)
    .SingleOrDefault();

return instance;

2) Измените все на использование атрибутов, например, в ответе SwiftingDuster

3) Используйте статический конструктор для создания словаря, который сопоставляет строковые предпочтения с конкретными типами. Отражение стоит дорого, и классы не изменятся (если вы не загружаете сборки динамически), так что сделайте это один раз.

Мы могли бы сделать это, используя (ab) мой предыдущий код «создать экземпляр, чтобы выбросить его», поэтому мы все еще создаем экземпляр каждого класса просто для чтения свойства, но, по крайней мере, мы делаем это только один раз :

static Dictionary<string, Type> prefixMapping;

static Program()
{
    prefixMapping = currentAssembly.GetTypes()
        .Where(t => t.IsSubclassOf(typeof(CommandBase)) && !t.IsAbstract)
        .Select(t => new { t, c = (CommandBase)Activator.CreateInstance(t) })
        .ToDictionary(x => x.t.GetProperty("Prefix").GetValue(x.c).ToString(), x => x.t);
}

static CommandBase GetInstance(string prefix)
{
    Type concreteType;
    if ( prefixMapping.TryGetValue(prefix, out concreteType) )
    {
        return (CommandBase)Activator.CreateInstance(concreteType);
    }
    return default(CommandBase);
}

Обратите внимание, что это вызовет действительно ужасное исключение, если у вас есть несколько классов с одинаковым префиксом, и, как это было бы исключением в статическом конструкторе, оно, скорее всего, взорвет ваше приложение. Либо перехватите исключение (которое оставит prefixMapping как null), либо измените выражение Linq, чтобы оно возвращало только один тип для каждого префикса (как показано ниже).

4) Используйте как метод Attribute из SwiftingDuster, так и предварительный расчет словаря. Это было бы моим предпочтительным решением:

static Dictionary<string, Type> prefixMapping;

static Program()
{
    prefixMapping = currentAssembly.GetTypes()
        .Where(t => t.IsSubclassOf(typeof(CommandBase)) && t.IsDefined(typeof(CommandAttribute), false) && !t.IsAbstract)
        .Select(t => new { t, p = t.GetCustomAttribute<CommandAttribute>().Prefix })
        .GroupBy(x => x.p)
        .ToDictionary(g => g.Key, g => g.First().t);
}

static CommandBase GetInstance(string prefix)
{
    Type concreteType;
    if ( prefixMapping.TryGetValue(prefix, out concreteType) )
    {
        return (CommandBase)Activator.CreateInstance(concreteType);
    }
    return default(CommandBase);
}

Надеюсь, это поможет

0 голосов
/ 05 мая 2018

Более понятный способ - определить собственный атрибут для хранения префикса.

[AttributeUsage(AttributeTargets.Class)]
public class CommandAttribute : Attribute
{
    public String Prefix { get; set; }

    public CommandAttribute(string commandPrefix)
    {
        Prefix = commandPrefix;
    }
}

Тогда используйте их так:

[CommandAttribute("P")]
public class PaintCommand : CommandBase
{}

[CommandAttribute("W")]
public class WalkCommand : CommandBase
{}

В отражении:

static CommandBase GetInstance(string prefix)
{       
    var currentAssembly = Assembly.GetExecutingAssembly();
    var concreteType = currentAssembly.GetTypes().Where(commandClass => commandClass.IsDefined(typeof(CommandAttribute), false) && commandClass.GetCustomAttribute<CommandAttribute>().Prefix == prefix).FirstOrDefault();

    if (concreteType == null)
        throw new InvalidCastException($"No concrete type found for command: {prefix}");

    return (CommandBase)Activator.CreateInstance(concreteType);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...