获取继承自抽象类的类的实例,并在属性中具有特定值

3
我正在开发一个工厂来创建基于两个标准的具体实例:

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 binder, Object[] parameters, CultureInfo culture) 在 System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)}

1
因此,要读取类实例属性,您需要向方法“t.GetProperty(“ Prefix”).GetValue(someInstance)”提供该实例的对象。显然,为了测试它而实例化每种类型是多余的。对于反射代码,我认为静态类属性而不是重写的实例属性可能更适合。鉴于您正在通过字符串查找(除非它具有另一个未记录的用途),继承的“Prefix”属性带来的好处不大。 - spender
2个回答

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
正如spender在他的评论中所提到的,你之所以会收到这个特定的错误是因为这一行代码:
t.GetProperty("Prefix").GetValue(t)

在这里,t 是一个包含类(例如 WalkCommand)的类型变量。您正在获取该类上的 Prefix 属性的 PropertyInfo 对象,然后尝试使用 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) 使用静态构造函数创建一个字典,将字符串前缀映射到具体类型。反射是昂贵的,而且这些类不会改变(除非您正在动态加载程序集),因此只需执行一次即可。

我们可以通过滥用我之前的“创建实例以将其丢弃”的代码来实现这一点,因此我们仍然需要创建每个类的实例才能读取属性,但至少我们只需要执行一次:

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

请注意,如果您有多个具有相同前缀的类,则会抛出一个非常可怕的异常,由于它是在静态构造函数中引发的异常,因此可能会导致应用程序崩溃。要么捕获异常(这将使prefixMappingnull),要么修改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);
}

希望这有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接