Тип XXX не присваивается услуге YYY - PullRequest
0 голосов
/ 06 мая 2019

Мне нужна помощь с настройкой шаблонов автозапуска.Я получаю ошибку ниже, которую я не могу обойти.У меня есть интерфейс с использованием обобщений под названием IScript<TOptionType>.

Реализация этого интерфейса представляет собой абстрактный класс с именем Script<TOptionType> : IScript<TOptionType>.Из этого абстрактного класса взяты два конкретных класса, которые устанавливают свои предпочтения TOptionType.

. Я загрузил пример основного приложения .net, в котором возникает проблема: https://github.com/Strandedpirate/agr

Для запускаперейдите к agr\autofac-generic-registration и введите dotnet run.

C #, похоже, не имеет проблем с упаковкой конкретных типов в интерфейс или абстрактный базовый класс.Так почему же здесь жалуется автофак?

C:\Users\strandedpirate\source\repos\agr\autofac-generic-registration (master -> origin)
λ dotnet run

Unhandled Exception: System.ArgumentException: The type 'agr.TableScript' is not assignable to service 'agr.Script`1'.
   at Autofac.Builder.RegistrationBuilder.CreateRegistration(Guid id, RegistrationData data, IInstanceActivator activator, Service[] services, IComponentRegistration target) in C:\projects\autofac\src\Autofac\Builder\RegistrationBuilder.cs:line 192
   at Autofac.Builder.RegistrationBuilder.CreateRegistration[TLimit,TActivatorData,TSingleRegistrationStyle](IRegistrationBuilder`3 builder) in C:\projects\autofac\src\Autofac\Builder\RegistrationBuilder.cs:line 132
   at Autofac.Builder.RegistrationBuilder.RegisterSingleComponent[TLimit,TActivatorData,TSingleRegistrationStyle](IComponentRegistry cr, IRegistrationBuilder`3 builder) in C:\projects\autofac\src\Autofac\Builder\RegistrationBuilder.cs:line 249
   at Autofac.ContainerBuilder.Build(IComponentRegistry componentRegistry, Boolean excludeDefaultModules) in C:\projects\autofac\src\Autofac\ContainerBuilder.cs:line 240
   at Autofac.ContainerBuilder.Build(ContainerBuildOptions options) in C:\projects\autofac\src\Autofac\ContainerBuilder.cs:line 148
   at agr.Program.Main(String[] args) in C:\Users\strandedpirate\source\repos\agr\autofac-generic-registration\Program.cs:line 22

Program.cs

class Program
    {
        static void Main(string[] args)
        {
            var builder = new ContainerBuilder();

            // comment out the next registrations to see the program run.
            builder.RegisterType<TableScript>()
              .As<Script<TableScriptOptions>>()
              .InstancePerLifetimeScope();
            builder.RegisterType<TableScript>()
              .As(typeof(Script<>))
              .InstancePerLifetimeScope();
            builder.RegisterType<TableScript>()
              .As(typeof(IScript<>))
              .InstancePerLifetimeScope();

            // explodes here during autofac building.
            var container = builder.Build();

            // if you comment out the above autofac configuration this will succeed compile and run-time.
            // why does c# have no problems converting TableScript to IScript<T> and Script<T> but autofac is complaining?
            TestInterface(new TableScript());
            TestAbstractBase(new TableScript());

            using (var scope = container.BeginLifetimeScope())
            {
                Console.WriteLine("Resolving IScript instance...");
                var instance = scope.Resolve(typeof(IScript<>)) as IScript<object>;
                instance.Run();
            }
        }

        static void TestInterface<T>(IScript<T> a)
            where T : class, new()
        {
            Console.WriteLine($"{nameof(TestInterface)} called - {a.CanHandle("table")}");
        }

        static void TestAbstractBase<T>(Script<T> a)
            where T : class, new()
        {
            Console.WriteLine($"{nameof(TestAbstractBase)} called - {a.CanHandle("table")}");
        }
    }

IScript.cs

public interface IScript<TOptionType>
        where TOptionType : class, new()
    {
        bool CanHandle(string key);
        Task Run();
        bool Validate(TOptionType options);
    }

Script.cs

public abstract class Script<TOptionType> : IScript<TOptionType>
        where TOptionType : class, new()
    {
        public abstract bool CanHandle(string key);

        public abstract Task Run();

        public virtual bool Validate(TOptionType options)
        {
            return true;
        }
    }

TableScript.cs


    public class TableScript : Script<TableScriptOptions>
    {
        public override bool CanHandle(string key)
        {
            return key == "table";
        }

        public override Task Run()
        {
            Console.WriteLine($"{nameof(TableScript)} executed");
            return Task.CompletedTask;
        }
    }

FileScript.cs

    public class FileScript : Script<FileScriptOptions>
    {
        public override bool CanHandle(string key)
        {
            return key == "file";
        }

        public override Task Run()
        {
            Console.WriteLine($"{nameof(FileScript)} executed");
            return Task.CompletedTask;
        }
    }

1 Ответ

1 голос
/ 06 мая 2019

Есть несколько проблем.

Во-первых, ваша TableScript регистрация:

builder.RegisterType<TableScript>()
       .As(typeof(Script<>))
       .InstancePerLifetimeScope();
builder.RegisterType<TableScript>()
       .As(typeof(IScript<>))
       .InstancePerLifetimeScope();

Вы пытаетесь зарегистрировать закрытый универсальный как открытый универсальный . Если подумать о том, что это значит, это все равно, что сказать «TableScript может разрешить любой T для Script<T> и IScript<T>». Например, я вижу TableScript : Script<TableScriptOptions> - то, что говорит открытая общая регистрация, должно так или иначе работать на Script<IntegerScriptOptions> или что-то еще, что могло бы попасть в эти угловые скобки.

Вместо этого зарегистрируйте его как закрытое универсальное. И я бы порекомендовал сделать это при одной и той же регистрации, иначе вы можете получить два разных экземпляра из TableScript за область действия в зависимости от того, какая служба разрешена.

builder.RegisterType<TableScript>()
       .As<Script<TableScriptOptions>>()
       .As<IScript<TableScriptOptions>>()
       .InstancePerLifetimeScope();

Далее разрешение IScript<T>:

scope.Resolve(typeof(IScript<>)) as IScript<object>;

Думайте о Resolve как о new. Если вы не можете использовать new или Activator.CreateInstance вместо него (в основном), то это не сработает. Например, вы не можете сделать это:

// This isn't a thing
new Script<>();

Вы также не можете поместить открытый универсальный объект в конструктор объекта, например, так:

public class MyClass
{
  // This also isn't a thing
  public MyClass(IScript<> script) { /* ... */ }
}

Вы не можете создать новый общий дженерик. Компилятор должен знать, что T находится в Script<T>. По тому же признаку, вы не можете разрешить открытый универсальный. Это не имеет смысла. Вы должны разрешить закрытый универсальный.

scope.Resolve<IScript<TableScriptOptions>>();

Если вы действительно хотите использовать рефлексию, вам все равно нужно сделать ее закрытой.

var script = typeof(IScript<>);
var options = typeof(TableScriptOptions);
var closed = script.MakeGenericType(new Type[] { options });
scope.Resolve(closed);
...