Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C#: cast to generic interface with base type

Tags:

c#

generics

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.

like image 293
queen3 Avatar asked Nov 17 '09 16:11

queen3


People also ask

What C is used for?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

What is C in C language?

What is C? C is a general-purpose programming language created by Dennis Ritchie at the Bell Laboratories in 1972. It is a very popular language, despite being old. C is strongly associated with UNIX, as it was developed to write the UNIX operating system.

What is the full name of C?

In the real sense it has no meaning or full form. It was developed by Dennis Ritchie and Ken Thompson at AT&T bell Lab. First, they used to call it as B language then later they made some improvement into it and renamed it as C and its superscript as C++ which was invented by Dr.

Is C language easy?

Compared to other languages—like Java, PHP, or C#—C is a relatively simple language to learn for anyone just starting to learn computer programming because of its limited number of keywords.


3 Answers

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

like image 139
Heinzi Avatar answered Oct 25 '22 19:10

Heinzi


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.

like image 26
Simon P Stevens Avatar answered Oct 25 '22 19:10

Simon P Stevens


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

like image 37
Chris Marisic Avatar answered Oct 25 '22 19:10

Chris Marisic