问题 C#:强制转换为基类型的通用接口


这是代码:

public interface IValidator<T>
{
   bool IsValid(T obj);
}

public class OrderValidator: IValidator<Order>
{
  // ...
}

public class BaseEntity
{
}

public class Order: BaseEntity
{
}

问题是我做不到:

var validator = new OrderValidator();
// this line throws because type can't be converted
var baseValidator = (IValidator<BaseEntity>)validator;
// all this is because I need a list with IValidator<Order>, IValidator<BaseOrder>, etc.
IList<IValidator<BaseEntity>> allValidators = ... 

如何获取并存储基础T的所有IValidator <T>实现的列表 - 比如BaseEntity?目前我做的是非通用的IValidator,接受“对象obj”,但它不好,不是类型安全的。

有趣的是C# 允许 编译:

var test = (IValidator<BaseEntity>)new OrderValidator();

但在运行时失败了

   Unable to cast object of type 'OrderValidator' to type 'IValidator`1[Orders.Core.BaseEntity]'

这是Windsor给我的同样的异常(我试过Windsor和手动类型查找,这个问题实际上与此无关,只与接口转换有关)。

感谢Heinzi,我现在看到为什么我无法施放 - 因为IValidator for Order期望Order为泛型类型。但是,如何返回不同类型的IValidator列表?原因是BaseEntity采用其真实类型并收集所有验证器 - 从GetType()到对象的所有类型。我真的想要一个通用的GetValidators(),然后对它进行操作。


12895
2017-11-17 16:04


起源

C#没有通配符(比如 IValidator<? extends BaseEntity>)? - Michael Myers♦
不,不是的 :( - David Seiler
如果所有其他方法都失败了,您可以使用 IList<object>。以来 IValidator<A> 和 IValidator<B> 没有共同的超类型,我认为你不会有另一种选择...(但我希望在这一点上被证明是错的。) - Heinzi


答案:


如果我解释为什么禁止这个演员,也许它可以帮助你:假设你有以下功能

void myFunc(IValidator<BaseEntity> myValidator) {
    myValidator.IsValid(new BaseEntity());
}

这段代码可以正确编译。不过,如果你通过了 OrderValidator 对于这个函数,你会得到一个运行时异常,因为 OrderValidator.IsValid 期望订单,而不是BaseEntity。如果您的演员被允许,将不再保持类型安全。

编辑:C#4允许 通用的共同和逆变,但这对你的情况没有帮助,因为你用T作为 输入 参数。因此,只有铸造到 IValidator<SomeSubtypeOfOrder> 可以以类型安全的方式完成。

所以,要清楚,你不能施展 OrderValidator 至 IValidator<BaseEntity> 因为OrderValidator只能验证订单,而不能验证所有类型的BaseEntities。然而,这是预期的 IValidator<BaseEntity>


11
2017-11-17 16:17



非常明确的解释,这无疑总结了为什么最好将所有这些工作留给IoC / DI框架。 - Chris Marisic
有关其他信息,请参阅更新 - queen3


演员不行,因为 IValidator<Order> 和 IValidator<BaseEntity> 完全不相关的类型。 IValidator<Order> 不是的子类型 IValidator<BaseEntity>,所以他们不能演员。

C#支持多接口继承,因此处理此问题的最简单方法是使订单验证器从两个验证器类型的接口继承,这样您就可以根据需要将其转换为任一接口。显然,这意味着你必须实现两个接口并指定如何处理基数 BaseEntity 提供的验证程序的类型不匹配。

像这样的东西:

public class OrderValidator : IValidator<Order>, IValidator<BaseEntity>
{
    public bool IsValid(Order obj)
    {
        // do validation
        // ...
        return true;
    }

    public bool IsValid(BaseEntity obj)
    {
        Order orderToValidate = obj as Order;
        if (orderToValidate != null)
        {
            return IsValid(orderToValidate);
        }
        else
        {
            // Eiter do this:
            throw new InvalidOperationException("This is an order validator so you can't validate objects that aren't orders");
            // Or this:
            return false;
            // Depending on what it is you are trying to achive.
        }
    }
}

这涉及到什么 Heinzi 说不能投,因为 IValidator<BaseEntity> 需要能够验证 BaseEntities,你当前的 OrderValidator 做不到。通过添加此多个接口,您可以明确定义验证行为 BaseEntities (通过明确地忽略它或导致异常),使得演员成为可能。


3
2017-11-17 16:23



这似乎与我的答案有关,但我的方法避免了很多语义限制,并允许对ValidationFactory.Validate(myOrder)进行泛型调用 - Chris Marisic


虽然这不会直接回答您,但我建议您查看StructureMap的源代码,它们可以使用开放的泛型类型。实际上甚至可能想使用StructureMap来处理验证器的缓存,这正是我所做的。

ForRequestedType(typeof (ValidationBase<>)).CacheBy(InstanceScope.Singleton);
Scan(assemblies =>
    {
        assemblies.TheCallingAssembly();
        assemblies.AddAllTypesOf(typeof(IValidation<>));
    });

然后我有一个工厂类来进行实际验证

public static class ValidationFactory
{
    public static Result Validate<T>(T obj)
    {
        try
        {
            var validator = ObjectFactory.GetInstance<IValidator<T>>();
            return validator.Validate(obj);
        }
        catch (Exception ex)
        {
            ...
        }
    }
}

编辑: 我写了一篇关于使用IoC进行泛型验证的大博文,如果你看一下,因为你说你已经使用过Spring,我打赌你可以调整我的工作来解决你的问题: 创建通用验证框架


2
2017-11-17 16:14



我实际上使用了Windsor和手动类型查找,因为这个问题都失败了(因为接口是无关的)。 - queen3
我添加了一篇关于通用验证框架主题的博客文章的链接,我认为它将帮助您解决问题,并且仍然可以调用Validate(myObject)并让一切解决正确。 - Chris Marisic
我用温莎,而不是春天。但我要看一下这篇文章。坦率地说,我很擅长IoC配置。 - queen3
你的文章太简单了。我正在实现的是,我需要为通用验证创建IValidator <Order>,并为衍生验证创建IValidator <SpecificOrder> - 甚至 一些 一些,例如OrderDateValidator,OrderProductsValidator ...现在,BaseEntity.Validate()将获取 所有 具体类型的验证器 - 用于SpecificOrder,用于Order,用于BaseEntity ......并执行所有这些,合并结果。这是为了分离不相关的验证类。因此,简单的GetInstance <IValidator <T >>不会这样做。 - queen3
在这种情况下,我的解决方案仍然基本相同,你需要做的就是你想要的。通过从中创建每个类,创建每个验证器OrderDateValidator,OrderProductsValidator IValidator<Order> 除了代替您将使用的GetInstance ObjectFactory.GetAllInstances<IValidator<T>>() 它会在IList中为您提供所有验证器,然后您可以根据需要处理它们。 - Chris Marisic