Reputation: 15521
Here's the code:
public interface IValidator<T>
{
bool IsValid(T obj);
}
public class OrderValidator: IValidator<Order>
{
// ...
}
public class BaseEntity
{
}
public class Order: BaseEntity
{
}
The problem is that I can't do:
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 = ...
How do I get and store a list of all implementations of IValidator<T> for base T - say, BaseEntity? Currently I do non-generic IValidator that accepts "object obj" but it is not good and not type-safe.
The funny stuff is that C# allows to compile:
var test = (IValidator<BaseEntity>)new OrderValidator();
but fails at runtime with
Unable to cast object of type 'OrderValidator' to type 'IValidator`1[Orders.Core.BaseEntity]'
This is the same exception that Windsor gives me (I tried both Windsor and manual types lookup, this issue is really not related to this, only to the interfaces casting).
Thanks to Heinzi, I see now why I can't cast - because IValidator for Order expects Order as generic type. But how do I return a list of IValidator for different types? The reason is that the BaseEntity takes its real type and gathers all validators - for all types from GetType() to the object. I'd really like to have a generic GetValidators() and then operate on it.
Upvotes: 12
Views: 7470
Reputation: 33128
While this won't answer you directly, I'd recommend taking a look at the source code for StructureMap, they do alot of work with open generic types. Actually might even want to use StructureMap to handle caching of your validators, this is exactly what i do.
ForRequestedType(typeof (ValidationBase<>)).CacheBy(InstanceScope.Singleton);
Scan(assemblies =>
{
assemblies.TheCallingAssembly();
assemblies.AddAllTypesOf(typeof(IValidation<>));
});
Then I have a factory class to do the actual validation
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)
{
...
}
}
}
Edit: I wrote a big blog post on generic validation using IoC, if you take a look at it since you said you already use Spring, I bet you could adapt my work to solve your problem: Creating a generic validation framework
Upvotes: 1
Reputation: 27509
The cast doesn't work because IValidator<Order>
and IValidator<BaseEntity>
are totally unrelated types. IValidator<Order>
is not a subtype of IValidator<BaseEntity>
, so they can't be cast.
C# does support multiple interface inheritance, so the simplest way to handle this is to make your order validator inherit from an interface for both validator types, that way it you will be able to cast it to either interface as required. Obviously this means you will have to implement both interfaces and specify how to handle the base when a BaseEntity
provided doesn't match the type the validator is for.
Something like this:
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.
}
}
}
This relates to what Heinzi says about not being able to cast because an IValidator<BaseEntity>
needs to be able to validate BaseEntities
, which your current OrderValidator
can't do. By adding this multiple interface you explicitly define the behaviour for validating BaseEntities
(by either explicitly ignoring it or causing an exception) so the cast becomes possible.
Upvotes: 3
Reputation: 172448
Maybe it helps you if I explain why this cast is forbidden: Assume that you have the following function
void myFunc(IValidator<BaseEntity> myValidator) {
myValidator.IsValid(new BaseEntity());
}
This code would compile correctly. Nevertheless, if you passed an OrderValidator
to this function, you would get a run-time exception, because OrderValidator.IsValid
expects an Order, not a BaseEntity. Type safety would no longer be maintained if your cast were allowed.
EDIT: C# 4 allows for generic co- and contravariance, but this would not help in your case, since you use T as an input parameter. Thus, only casting to an IValidator<SomeSubtypeOfOrder>
could be done in a type-safe way.
So, to be clear, you cannot cast OrderValidator
to IValidator<BaseEntity>
because your OrderValidator can only validate orders, not all kinds of BaseEntities. This, however, is what would be expected of an IValidator<BaseEntity>
.
Upvotes: 11