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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了获取从抽象类继承并在属性中具有特定值的类的实例相关的知识,希望对你有一定的参考价值。

我正在一家工厂根据两个标准创建具体实例:

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文化)

答案

更简洁的方法是定义自己的属性来存储前缀。

[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);
}
另一答案

正如他在评论中提到的消费者所说,你得到这个特定错误的原因是这一行:

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)使用静态构造函数创建一个将字符串前缀映射到具体类型的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);
}

希望这可以帮助

以上是关于获取从抽象类继承并在属性中具有特定值的类的实例的主要内容,如果未能解决你的问题,请参考以下文章

Java 继承

Typescript中的类

java早期绑定的关键词都有哪些

Java Review (十面向对象----抽象类)

Java Review (十面向对象----抽象类)

抽象类和接口