C #: приведение к универсальному интерфейсу с базовым типом - PullRequest
11 голосов
/ 17 ноября 2009

Вот код:

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 - скажем, BaseEntity?В настоящее время я использую неуниверсальный IValidator, который принимает «объектный объект», но он не очень хорош и не безопасен для типов.

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

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

, но во время выполнения происходит сбой с

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

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

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

Ответы [ 3 ]

10 голосов
/ 17 ноября 2009

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

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

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

РЕДАКТИРОВАТЬ: C # 4 допускает общего ко-и контравариантности , но это не поможет в вашем случае, так как вы используете T в качестве input параметра. Таким образом, только приведение к IValidator<SomeSubtypeOfOrder> может быть выполнено безопасным для типов способом.

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

3 голосов
/ 17 ноября 2009

Приведение не работает, потому что 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.
        }
    }
}

Это относится к тому, что Хайнци говорит о невозможности сотворения, потому что IValidator<BaseEntity> должен иметь возможность проверить BaseEntities, что не может сделать ваш текущий OrderValidator. Добавляя этот множественный интерфейс, вы явно определяете поведение для проверки BaseEntities (либо явно игнорируя его, либо вызывая исключение), так что приведение становится возможным.

2 голосов
/ 17 ноября 2009

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...