Я не думаю, что вы сможете делать то, что вы хотите. Извините, возможно, не тот ответ, который вам нужен. Я покажу вам, почему, и, возможно, некоторые обходные пути, но наличие произвольной коллекции закрытых генериков, которые не закрываются до тех пор, пока разрешение не станет на самом деле вещью.
Давайте на секунду проигнорируем DI и просто рассмотрим FruitService
, который я не вижу в этом вопросе, но который мы видим в использовании здесь:
builder.Register(c => new FruitService(c.ResolveNamed("appleService")))
.As<IDbMapperService>();
Обратите внимание, мы видим, что FruitService
реализует IDbMapperService
, потому что он зарегистрирован как этот интерфейс.
Кроме того, мы можем видеть, что FruitService
выглядит так, как будто это требует некоторого набора вещей, поскольку в примере регистрации есть две вещи, названные одинаковыми.
builder.RegisterType<SaveApplDbWrapper>().As<DbWrapper<AppleDto, SavedAppleDbEntity>>()
.Named<string>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>().As<IUcHandler<OrangeDto, OrangeDbEntity>>()
.Named<string>("fruitService");
Я заметил, что оба они реализуют различные универсальные типы. Основываясь на остальной части вопроса, я должен предположить, что у них нет общего базового класса.
Чтобы сделать его более конкретным и обойти часть Autofac, которая, на мой взгляд, не имеет отношения к более крупной проблеме, давайте рассмотрим это так:
var wrapper = new[] { CreateWrapper("appleService"), CreateHandler("appleService") };
var service = new FruitService(wrapper);
Предположим, CreateWrapper
и CreateHandler
оба берут строку и, магическим образом, создают соответствующие типы оболочки / обработчика. Неважно, как это происходит.
Здесь следует рассмотреть две вещи, которые тесно связаны:
- Какой тип параметра в конструкторе
FruitService
?
- Что вы ожидаете
CreateWrapper("appleService")
и CreateHandler("appleService")
, чтобы вернуться?
Здесь я вижу только два варианта.
Вариант 1: Использовать object
.
Если нет общего базового класса, то все должно быть object
.
public class FruitService : IDBMapperService
{
private readonly IEnumerable<object> _wrappers;
public FruitService(IEnumerable<object>wrapper)
{
this._wrapper = wrapper;
}
public object GetWrapper<TInput, TResult>()
{
object foundWrapper = null;
// Search through the collection using a lot of reflection
// to find the right wrapper, then
return foundWrapper;
}
}
Не ясно, что DbWrapper<TInput, TResult>
можно привести к IUcHandler<TInput, TResult>
, поэтому вы даже не можете на это полагаться. Там нет общности.
Но скажем, есть общий базовый класс.
Вариант 2: Используйте общий базовый класс
Кажется, уже существует понятие DbWrapper<TInput, TResult>
. Важно отметить, что даже если у вас есть этот общий род, как только вы закроете его, это два разных типа. DbWrapper<AppleDto, SavedAppleDbEntity>
не может быть преобразовано в DbWrapper<OrangeDto, SavedOrangeDbEntity>
. Дженерики больше похожи на «шаблоны классов», чем на базовые классы. Они не одно и то же.
Вы не можете, например, сделать:
var collection = new DbWrapper<,>[]
{
new DbWrapper<AppleDto, SavedAppleDbEntity>(),
new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};
Однако, если у вас общий интерфейс или базовый класс, вы можете сделать ...
var collection = new IDbWrapper[]
{
new DbWrapper<AppleDto, SavedAppleDbEntity>(),
new DbWrapper<OrangeDto, SavedOrangeDbEntity>()
};
Но это означало бы, что вы можете переключиться на это и, якобы, использовать общий интерфейс.
public class FruitService : IDBMapperService
{
private readonly IEnumerable<object> _wrappers;
public FruitService(IEnumerable<object>wrapper)
{
this._wrapper = wrapper;
}
public IDbWrapper GetWrapper<TInput, TResult>()
{
IDbWrapper foundWrapper = null;
// Search through the collection using a lot of reflection
// to find the right wrapper, then
return foundWrapper;
// IDbWrapper could expose those `TInput` and `TResult`
// types as properties on the interface, so the reflection
// could be super simple and way more straight LINQ.
}
}
Ваш потребляющий код может просто взять IDbWrapper
и вызвать неуниверсальные методы для достижения цели.
Возвращаем его в автофактуру ...
Помните, я упоминал, что ключ к определению того, что должны возвращать методы Create
; или что ожидает конструктор FruitService
? Тот. Это в пики.
Вы можете зарегистрировать все как ключевые объекты.
builder.RegisterType<SaveApplDbWrapper>()
.Named<object>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
.Named<object>("fruitService");
builder.RegisterType<SaveMelon>()
.Named<object>("appleService");
builder
.Register(c => new FruitService(c.ResolveNamed<IEnumerable<object>>("appleService")))
.As<IDbMapperService>();
Операции Resolve
в Autofac являются методами создания из моего примера. Там нет магии там; это просто создание объектов. Вам все еще нужно знать, какой тип вы хотите предоставить.
Или вы можете использовать общий базовый класс.
builder.RegisterType<SaveApplDbWrapper>()
.Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveOrangeDbWrapper>()
.Named<IDbWrapper>("fruitService");
builder.RegisterType<SaveMelon>()
.Named<IDbWrapper>("appleService");
builder
.Register(c => new FruitService(c.ResolveNamed<IEnumerable<IDbWrapper>>("appleService")))
.As<IDbMapperService>();
Если вы не возражаете смешивать систему DI с FruitService
, вы можете сделать что-то вроде этого:
public class FruitService
{
private readonly ILifetimeScope _scope;
public FruitService(ILifetimeScope scope)
{
this._scope = scope;
}
public DbWrapper<TInput, TResult> GetWrapper<TInput, TResult>()
{
var type = typeof(DbWrapper<TInput, TResult>);
var wrapper = this._lifetimeScope.Resolve(type);
return wrapper;
}
}
Вы должны были бы зарегистрировать вещи без их имен и As
a DbWrapper
, но это сработало бы, если бы все основывалось на этом.
builder.RegisterType<SaveApplDbWrapper>()
.As<DbWrapper<AppleDto, SavedAppleDbEntity>>();
// Must be DbWrapper, can also be other things...
builder.RegisterType<SaveOrangeDbWrapper>()
.As<IUcHandler<OrangeDto, OrangeDbEntity>>()
.As<DbWrapper<OrangeDto, OrangeDbEntity>>();
builder.RegisterType<SaveMelon>()
.As<DbWrapper<MelonDto, MelonDbEntity>>()
.As<IUcHandler<MelonDto, MelonDbEntity>>();
builder.RegisterType<FruitService>()
.As<IDbMapperService>();
При разрешении IDbMapperService
конструктор FruitService
получит ссылку на область действия времени жизни, из которой он был разрешен. Все обертки будут разрешены из той же области.
Людям, как правило, не нравится смешивать ссылки IoC в своем коде, как это, но это единственный способ, как я вижу, вы избавитесь от необходимости возиться с отражением или повышением или понижением во всем.
Удачи!