С#: преобразование в общий интерфейс с базовым типом

Здесь код:

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 <T> для базы T - скажем, BaseEntity? В настоящее время я делаю не общий идентификатор, который принимает "объект obj", но это не хорошо, а не безопасно.

Забавно, что С# позволяет компилировать:

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

но сбой при выполнении

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

Это то же самое исключение, которое дает Windsor (я пробовал поиск по Windsor и ручному типу, эта проблема действительно не связана с этим, только с линией интерфейса).

Благодаря Хайнци, теперь я вижу, почему я не могу бросить - потому что Ивалидатор для Ордера ожидает, что порядок будет использоваться как общий тип. Но как мне вернуть список IValidator для разных типов? Причина в том, что BaseEntity принимает свой реальный тип и собирает все валидаторы - для всех типов из GetType() для объекта. Я действительно хотел бы иметь общий GetValidators(), а затем работать с ним.

Ответ 1

Возможно, это поможет вам, если я объясню, почему этот актер запрещен: предположим, что у вас есть следующая функция

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

Этот код будет скомпилирован правильно. Тем не менее, если вы передали OrderValidator этой функции, вы получили бы исключение во время выполнения, потому что OrderValidator.IsValid ожидает Order, а не BaseEntity. Безопасность типов больше не будет поддерживаться, если ваш бросок будет разрешен.

EDIT: С# 4 позволяет общую ко- и контравариантность, но это не поможет в вашем случае, поскольку вы используете T в качестве входного параметр. Таким образом, только кастинг на IValidator<SomeSubtypeOfOrder> может быть выполнен безопасным способом.

Итак, чтобы быть понятным, вы не можете отбрасывать OrderValidator в IValidator<BaseEntity>, потому что ваш OrderValidator может только проверять заказы, а не все типы BaseEntities. Это, однако, то, что можно было бы ожидать от IValidator<BaseEntity>.

Ответ 2

Приведение не работает, потому что IValidator<Order> и IValidator<BaseEntity> являются полностью несвязанными типами. IValidator<Order> не является подтипом IValidator<BaseEntity>, поэтому их нельзя отличить.

С# поддерживает множественное наследование интерфейса, поэтому самый простой способ справиться с этим состоит в том, чтобы сделать ваш валидатор порядка наследованием от интерфейса для обоих типов валидатора, таким образом, вы сможете придать его любому интерфейсу по мере необходимости. Очевидно, это означает, что вам придется реализовать оба интерфейса и указать, как обращаться с базой, если предоставленный 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

Пока это не ответит вам напрямую, я бы рекомендовал взглянуть на исходный код для StructureMap, они много работают с открытыми родовыми типами. На самом деле, возможно, даже захотите использовать StructureMap для обработки кэширования ваших валидаторов, это именно то, что я делаю.

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

Затем у меня есть класс factory, чтобы выполнить фактическую проверку

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, я уверен, вы могли бы адаптировать мою работу для решения ваша проблема: Создание общей рамки проверки