В C#: Как объявить универсальный Словарь с типом, столь же ключевым и 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);

Как определить такой словарь?

26
задан Marcel 17 May 2011 в 09:53
поделиться

6 ответов

Возможно, вам даже не понадобится словарь, чтобы иметь возможность делать это - но это зависит от ваших потребностей. Если вам нужен только один такой список для каждого типа на appdomain (т.е. "словарь" является статическим), то следующий паттерн может быть эффективным и хорошо продвигает вывод типов:

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, включая IBase, без ошибок во время выполнения или компиляции - что может быть особенностью или ошибкой, которая только и ждет своего часа, так что вы можете захотеть if для проверки этого.

Кроме того, этот подход игнорирует потоки; поэтому если вы хотите получить доступ к этой структуре данных из нескольких потоков, вам, вероятно, потребуется добавить блокировку. Чтение/запись ссылок атомарны, так что вы не получите повреждений, если не заблокируете, но неактуальные данные и условия гонки, конечно, возможны.

16
ответ дан 28 November 2019 в 06:35
поделиться

Создайте собственный класс словаря:

public class BaseClassDictionary<T, IEnumerable<T>> : Dictionary<T, IEnumerable<T>>
    where T : BaseClass
{
}

Затем вы можете использовать этот специализированный словарь вместо типа поля:

private BaseClassDictionary<BaseClassDerivedType, IEnumerable<BaseClassDerivedType>> myDictionary;
4
ответ дан 28 November 2019 в 06:35
поделиться

Вы не ограничиваете T в частном члене; вы ограничиваете его на уровне класса.

class Foo<T> where T : BaseClass
{
    private IDictionary<T, IEnumerable<T>> _dataOfType;

    public Foo(IDictionary<T, IEnumerable<T>> dataOfType)
    {
        this._dataOfType = dataOfType;
    }   
}   
11
ответ дан 28 November 2019 в 06:35
поделиться
  1. Вы не можете ограничить конкретную переменную. Он работает только с классами и методами. Честно говоря, это не имеет никакого смысла на переменном уровне.
  2. Вам нужен собственный класс - класс WeirdDictionary: IDictionary , который перегрузит метод Add, чтобы принять Type и IEnumerable этого типа, что можно сделать с помощью ограничений. и приведите IEnumerable <> к IEnumerable. Также перегрузите индексатор и верните его к соответствующему типу.
    Все это преобразование необходимо, поскольку универсальные шаблоны строго следят за тем, чтобы IEnumerable был так же хорош, как IEnumerable (Полагаю, это называется дисперсией?)

Это решение является немного обобщенным, так как повторное использование камней

Править 280Z28:

Сначала я пометил это, потому что пункт 2 сбивал с толку и неправильно его истолковал. Используя явную реализацию методов в IDictionary и предоставляя общие альтернативы, вы можете получить довольно чистый интерфейс. Обратите внимание, что вы не можете создавать универсальные индексаторы, поэтому вам всегда придется использовать TryGet (что в любом случае является хорошей идеей). Я только включил явную реализацию одного из методов IDictionary <> , чтобы показать, как выполнять проверки. Не создавайте WeirdDictionary напрямую из Dictionary , иначе вы потеряете возможность гарантировать ограничения в базовых данных.

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

7
ответ дан 28 November 2019 в 06:35
поделиться

Попробуйте следующее:

public class MyCustomDictionary<T>: Dictionary<T, IEnumerable<T>>  { } 
1
ответ дан 28 November 2019 в 06:35
поделиться

Используйте System.ComponentModel.Design.ServiceContainer , который уже доступен в платформе .Net.

        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);
25
ответ дан 28 November 2019 в 06:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: