RegisterOpenGeneric с SimpleInjector разрешает неверный тип

Мы начнем с отдельной истории, просто чтобы вы поняли, почему: я хочу рассматривать любые действия, которые изменяют данные, в одном и том же интерфейсе: ICommand Существуют вещи, называемые ICommandHandlers, которые обрабатывают любую команду, которую я захочу. Итак, если я хочу CreatePersonCommand, мне нужен CreatePersonCommandHandler.

Итак, вот тело консольного приложения, которое демонстрирует это: (Требуется Simple Injector)

// The e.g. CreatePersonCommand, with TResult being Person, as an example.
public interface ICommand<TResult>
{
}

//This handles the command, so CreatePersonCommandHandler
public interface ICommandHandler<in TCommand, out TResult>
    where TCommand : ICommand<TResult>
{
    TResult Handle(TCommand command);
}

// Imagine a generic CRUD set of operations here where we pass 
// in an instance of what we need made
public class CreateBaseCommand<TModel> : ICommand<TModel>
{
    public TModel ItemToCreate { get; set; }
}

public class DeleteBaseCommand<TModel> : ICommand<TModel>
{
    public TModel ItemToDelete { get; set; }
}

public class CreateCommandBaseHandler<TModel> 
    : ICommandHandler<CreateBaseCommand<TModel>, TModel>
{
    public TModel Handle(CreateBaseCommand<TModel> command)
    {
        // create the thing
        return default (TModel);
    }
}

public class DeleteCommandBaseHandler<TModel> 
    : ICommandHandler<DeleteBaseCommand<TModel>, TModel>
{
    public TModel Handle(DeleteBaseCommand<TModel> command)
    {
        // delete the thing
        return default(TModel);
    }
}

public class Program
{
    private static Container container;

    static void Main(string[] args)
    {
        container = new Container();

        // Order does not seem to matter, I've tried both ways.
        container.RegisterOpenGeneric(typeof(ICommandHandler<,>),
            typeof(DeleteCommandBaseHandler<>));
        container.RegisterOpenGeneric(typeof(ICommandHandler<,>),
            typeof(CreateCommandBaseHandler<>));

        container.Verify();

        // So I want to make the usual hello world
        var commandToProcess = new CreateBaseCommand<string> { ItemToCreate = "hello world"};

        // Send it away!
        Send(commandToProcess);
    }

    private static void Send<TResult>(ICommand<TResult> commandToProcess)
    {
        //{CreateBaseCommand`1[[System.String,..."}
        var command = commandToProcess.GetType();
        //{Name = "String" FullName = "System.String"}
        var resultType = typeof (TResult);

        //"ICommandHandler`2[[CreateBaseCommand`1[[System.String,..."}
        // so it's the right type here
        var type = typeof(ICommandHandler<,>).MakeGenericType(command, resultType); 

        // This is where we break!
        var instance = container.GetInstance(type);
        // The supplied type DeleteCommandBaseHandler<String> does not implement 
        // ICommandHandler<CreateBaseCommand<String>, String>.
        // Parameter name: implementationType
    }
}

Поэтому по какой-то причине SimpleInjector всегда пытается разрешить DeleteCommandHandler<> для CreateBaseCommand<>, которые у меня есть. Опять же, порядок не имеет значения. У меня есть другие обработчики команд закрытого типа (и соответствующие им команды), которые просто наследуют ICommandHandler<,> и работают нормально.

Я провел немало времени, изучая все возможные типы регистрации, какие мог, из этого.


person Matt V    schedule 18.10.2013    source источник
comment
Ошибка, с которой вы столкнулись, действительно раздражает. Я только что столкнулся с той же проблемой при написании декоратора в одном из моих собственных приложений. Я рассматриваю возможность выпуска патча (2.3.6) для исправления этой ошибки, вместо того, чтобы ждать следующего минорного выпуска (2.4), поскольку в некоторых сценариях очень сложно обойти эту ошибку.   -  person Steven    schedule 21.10.2013
comment
Это хорошая новость, я сам не ждал других дел.   -  person Matt V    schedule 21.10.2013


Ответы (1)


ОБНОВЛЕНИЕ:

Это определенно ошибка текущего релиза. Это каким-то образом проскользнуло сквозь трещины модульного тестирования. Код пропускает проверку, которая проверяет, действительно ли встроенная закрытая универсальная реализация реализует запрошенный закрытый универсальный тип службы. Если все ограничения универсального типа допустимы, фреймворк считает разрешение успешным, что неверно в вашем случае.

Исправление довольно простое, и грядущая версия 2.4 определенно исправит это, но пока вам придется использовать следующий обходной путь.

ОБНОВЛЕНИЕ 2:

На самом деле это довольно неприятно, и в некоторых случаях это может быть довольно сложно обойти. Помимо RegisterOpenGeneric, также затрагиваются регистрации декораторов. Это заставило меня сделать вывод, что это должно быть исправлено быстро и не может ждать до следующего минорного релиза. Поэтому я отправил Версию 2.3.6 в NuGet и CodePlex. v2.3.6 исправляет эту проблему.

ВРЕМЕННОЕ РЕШЕНИЕ:

Обходной путь заключается в том, чтобы предотвратить предоставление аргументов универсального типа, вложенных в другие типы (например, ваш DeleteBaseCommand<TModel>). Вместо этого вы можете вернуться к использованию ограничений универсального типа, как показано в следующем примере:

public class CreateCommandBaseHandler<TCommand, TModel> 
    : ICommandHandler<TCommand, TModel> // no nested generic arguments here
    where TCommand : CreateBaseCommand<TModel> // but type constraint here.
{
    public TModel Handle(TCommand command)
    {
        // create the thing
        return default(TModel);
    }
}

public class DeleteCommandBaseHandler<TCommand, TModel> 
    : ICommandHandler<TCommand, TModel>
    where TCommand : DeleteBaseCommand<TModel>
{
    public TModel Handle(TCommand command)
    {
        // delete the thing
        return default(TModel);
    }
}
person Steven    schedule 18.10.2013