В С#: Как объявить общий словарь с типом как ключ и IEnumerable <> этого типа в качестве значения?
Я хочу объявить словарь, который хранит типизированный IEnumerable
определенного типа, с этим точным типом как ключ, например: (Отредактировано, чтобы следовать комментариям johny g)
private IDictionary<Type, IEnumerable<T>> _dataOfType where T: BaseClass; //does not compile!
Конкретные классы, которые я хочу сохранить, все вытекают из BaseClass, поэтому идея использовать его как ограничение. Компилятор жалуется, что он ожидает точку с запятой после имени участника.
Если бы это сработало, я бы ожидал, что это сделает последующий поиск из словаря простым:
IEnumerable<ConcreteData> concreteData;
_sitesOfType.TryGetValue(typeof(ConcreteType), out concreteData);
Как определить такой словарь?
Ответы
Ответ 1
Вам может и не понадобиться словарь для этого, но это зависит от ваших потребностей. Если вам понадобится только один такой список для каждого типа приложения (например, "словарь" static
), следующий шаблон может быть эффективен и хорошо подходит для ввода типа:
interface IBase {}
static class Container {
static class PerType<T> where T : IBase {
public static IEnumerable<T> list;
}
public static IEnumerable<T> Get<T>() where T : IBase
=> PerType<T>.list;
public static void Set<T>(IEnumerable<T> newlist) where T : IBase
=> PerType<T>.list = newlist;
public static IEnumerable<T> GetByExample<T>(T ignoredExample) where T : IBase
=> Get<T>();
}
Обратите внимание, что перед принятием этого подхода следует тщательно подумать о различии между типом времени компиляции и типом времени выполнения. Этот метод с радостью позволит вам сохранить переменную IEnumerable<SomeType>
, определенную во время выполнения, как в SomeType
, так и при ее использовании под любым из базовых типов SomeType
, включая IBase
, без какой-либо ошибки времени выполнения или ошибки типа компиляции может быть функцией или ошибкой, ожидающей своего появления, поэтому вам может понадобиться if
проверить это.
Кроме того, этот подход игнорирует потоки; поэтому, если вы хотите получить доступ к этой структуре данных из нескольких потоков, вы, вероятно, захотите добавить некоторую блокировку. Ссылки на чтение/запись являются атомарными, поэтому вы не получите коррупции, если вы не заблокируете, но, конечно, возможны устаревшие данные и условия гонки.
Ответ 2
Используйте System.ComponentModel.Design.ServiceContainer
, который уже доступен в .NET Framework.
ServiceContainer container = new ServiceContainer();
IList<int> integers = new List<int>();
IList<string> strings = new List<string>();
IList<double> doubles = new List<double>();
container.AddService(typeof(IEnumerable<int>), integers);
container.AddService(typeof(IEnumerable<string>), strings);
container.AddService(typeof(IEnumerable<double>), doubles);
Ответ 3
Вы не ограничиваете T
в частном члене; вы ограничиваете его на уровне класса.
class Foo<T> where T : BaseClass
{
private IDictionary<T, IEnumerable<T>> _dataOfType;
public Foo(IDictionary<T, IEnumerable<T>> dataOfType)
{
this._dataOfType = dataOfType;
}
}
Ответ 4
- Вы не можете ограничить определенную переменную. Он работает только по классам и методам. Честно говоря, это действительно не имеет никакого смысла на переменном уровне.
- Вы хотите создать собственный класс -
class WeirdDictionary : IDictionary<Type, IEnumerable>
, который будет перегружать метод Add, чтобы взять тип и IEnumerable этого типа, которые вы можете использовать с помощью ограничений, и отбросить IEnumerable < > to IEnumerable. Также перегрузите указатель и верните его соответствующему типу.
Все это кастинг необходимо, так как generics строгие относительно IEnumerable<Base>
, как хорошие, как IEnumerable<Derived>
(Это называется дисперсией, я верю?)
Это решение слегка обобщено, так как reuse rocks
Изменить по 280Z28:
Сначала я отметил это, потому что точка № 2 была запутанной, и я неверно истолковал ее. Используя явную реализацию методов в IDictionary<Type, IEnumerable>
и предоставляя общие альтернативы, вы можете получить довольно чистый интерфейс. Обратите внимание, что вы не можете создавать родовые индексы, поэтому вам всегда нужно использовать TryGet<T>
(что в любом случае является хорошей идеей). Я только включил явную реализацию одного из методов IDictionary<>
, чтобы показать, как выполнять проверки. Не выводить WeirdDictionary
непосредственно из Dictionary<Type, IEnumerable>
или вы потеряете возможность гарантировать ограничения в базовых данных.
class WeirdDictionary : IDictionary<Type, IEnumerable>
{
private readonly Dictionary<Type, IEnumerable> _data =
new Dictionary<Type, IEnumerable>();
public void Add<T>(IEnumerable<T> value)
{
_data.Add(typeof(T), value);
}
public bool TryGet<T>(out IEnumerable<T> value)
{
IEnumerable enumerable;
if (_data.TryGetValue(typeof(T), out enumerable)
{
value = (IEnumerable<T>)enumerable;
return true;
}
value = null;
return false;
}
// use explicit implementation to discourage use of this method since
// the manual type checking is much slower that the generic version above
void IDictionary<Type, IEnumerable>.Add(Type key, IEnumerable value)
{
if (key == null)
throw new ArgumentNullException("key");
if (value != null && !typeof(IEnumerable<>).MakeGenericType(key).IsAssignableFrom(value.GetType()))
throw new ArgumentException(string.Format("'value' does not implement IEnumerable<{0}>", key));
_data.Add(key, value);
}
}
Конец 280Z28
Ответ 5
Создайте собственный класс словаря:
public class BaseClassDictionary<T, IEnumerable<T>> : Dictionary<T, IEnumerable<T>>
where T : BaseClass
{
}
Затем вы можете использовать этот специализированный словарь вместо типа поля:
private BaseClassDictionary<BaseClassDerivedType, IEnumerable<BaseClassDerivedType>> myDictionary;
Ответ 6
Попробуйте следующее:
public class MyCustomDictionary<T>: Dictionary<T, IEnumerable<T>> { }