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

时间:2009-11-17 16:04:03

标签: c# generics

以下是代码:

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 = ... 

如何获取和存储IValidator&lt; T&gt;的所有实现的列表?对于基地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(),然后对它进行操作。

3 个答案:

答案 0 :(得分:10)

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

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

此代码可以正确编译。但是,如果您将OrderValidator传递给此函数,则会出现运行时异常,因为OrderValidator.IsValid需要订单而不是BaseEntity。如果您的演员被允许,将不再保持类型安全。

编辑:C#4允许generic co- and contravariance,但这对您的情况没有帮助,因为您使用T作为输入参数。因此,只能以类型安全的方式进行IValidator<SomeSubtypeOfOrder>的转换。

因此,要清楚,您无法将OrderValidator强制转换为IValidator<BaseEntity>,因为您的OrderValidator只能验证订单,而不能验证所有类型的BaseEntities。然而,这是IValidator<BaseEntity>所期望的。

答案 1 :(得分:3)

演员表不起作用,因为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的行为(通过显式忽略它或导致异常),这样就可以进行强制转换。

答案 2 :(得分:2)

虽然这不会直接回答您,但我建议您查看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,我打赌你可以调整我的工作来解决你的问题:Creating a generic validation framework