Класс SearchableServiceFactory<A, B>
не совпадает с классом
SearchableServiceFactory<X, Y>
.Следовательно, вы имеете дело с двумя различными наборами статических элементов.В частности, у вас есть два разных словаря _SearchableLookupServicesRegistry
.
. Вы регистрируетесь в одном из них (в SearchableServiceFactory<OrgLookupService, OrgOutputDto>
).Другой (в
SearchableServiceFactory<ISearchableLookupService<IBaseOutputDto>, IBaseOutputDto>
) остается пустым, но вы пытаетесь использовать его для извлечения конструктора.Если вы установите точку останова для оператора throw
в Create
и осмотрите _SearchableLookupServicesRegistry
, вы увидите, что Count
равно 0
.
Проблема с дженериками заключается в том, что они могут отображатьсяпредлагают некоторое динамическое поведение, но они этого не делают.Все параметры универсального типа определяются временем компиляции.Сложные сценарии, использующие дженерики, часто становятся очень запутанными.Если вам нужна высокая динамичность, иногда вы должны отказаться от полной безопасности типов.
Это мое предложение для фабрики услуг:
public static class SearchableServiceFactory
{
static readonly Dictionary<string, Func<ISearchableLookupService<IBaseOutputDto>>>
_SearchableLookupServicesRegistry =
new Dictionary<string, Func<ISearchableLookupService<IBaseOutputDto>>>();
public static TSearchableLookupService Create<TSearchableLookupService>(string key)
where TSearchableLookupService : ISearchableLookupService<IBaseOutputDto>
{
if (_SearchableLookupServicesRegistry.TryGetValue(
key,
out Func<ISearchableLookupService<IBaseOutputDto>> searchableServiceConstructor))
{
return (TSearchableLookupService)searchableServiceConstructor();
}
throw new ArgumentException($"Service for \"{key}\" not registered.");
}
public static void Register(
string key,
Func<ISearchableLookupService<IBaseOutputDto>> searchableServiceConstructor)
{
_SearchableLookupServicesRegistry.Add(key, searchableServiceConstructor);
}
}
Обратите внимание, что SearchableServiceFactory
не является универсальным.Это необходимо для того, чтобы иметь только одну фабрику и, следовательно, только один статический словарь.
В этом модифицированном интерфейсе используется модификатор out
.Модификатор out
добавляет ковариацию.Т.е. вы можете указать для него производный тип;однако универсальный тип, украшенный им, должен появляться только как тип возвращаемого значения или в параметрах out
.
public interface ISearchableLookupService<out TOutputDto> where TOutputDto : IBaseOutputDto
{
TOutputDto GetOutputDto();
}
Вы можете зарегистрироваться с помощью
SearchableServiceFactory.Register(
nameof(Organization), () => new OrgLookupService(_Context, _OrganizationRepository));
и создать с помощью
IBaseOutputDto result = SearchableServiceFactory
.Create<ISearchableLookupService<IBaseOutputDto>>(nameof(Organization))
.GetOutputDto();
или для получения более конкретного типа
OrgOutputDto result = SearchableServiceFactory
.Create<ISearchableLookupService<OrgOutputDto>>(nameof(Organization))
.GetOutputDto();
Но этот последний пример делает строковый ключ nameof(Organization)
избыточным, поскольку сам тип OrgOutputDto
может использоваться в качестве ключа.(Мне понадобится Reflection, чтобы извлечь его из TSearchableLookupService
.)