我正在一家工厂根据两个标准创建具体实例:
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:Object与目标类型不匹配。 System.Reflection.RuntimeMethodInfo.Invoke的System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj,BindingFlags invokeAttr,Binder binder,Object []参数,CultureInfo文化)中的System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target)处于System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr,Binder binder,Object []参数,CultureInfo文化)
答案 0 :(得分:5)
更简洁的方法是定义自己的属性来存储前缀。
[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);
}
答案 1 :(得分:1)
正如消费者在评论中提到的,你得到这个特定错误的原因是这一行:
t.GetProperty("Prefix").GetValue(t)
此处,t
是包含类的Type变量,例如WalkCommand
。您正在获取该类的PropertyInfo
属性的Prefix
对象,然后尝试使用GetValue()
从WalkCommand
对象的实例中读取该属性的值
问题是你没有传递GetValue()
WalkCommand
类的实例,你传递的是Type
所以反射会抛出这个异常。
有几种方法可以解决这个问题:
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)使用静态构造函数创建一个将字符串前缀映射到具体类型的Dictionary。反射是昂贵的,并且类不会改变(除非你动态加载程序集),所以这样做一次。
我们可以通过(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)同时使用SwiftingDuster中的Attribute方法以及字典的预计算。这将是我的首选解决方案:
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);
}
希望这有帮助