Как отметил спонсор в своем комментарии, причиной этой конкретной ошибки является следующая строка:
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);
}
Надеюсь, это поможет