Общий класс C# со «специализированным» конструктором

У меня есть такой класс:

public class DropDownControl<T, Key, Value> : BaseControl
    where Key: IComparable
{
    private IEnumerable<T> mEnumerator;
    private Func<T, Key> mGetKey;
    private Func<T, Value> mGetValue;
    private Func<Key, bool> mIsKeyInCollection;

    public DropDownControl(string name, IEnumerable<T> enumerator, Func<T, Key> getKey, Func<T, Value> getValue, Func<Key, bool> isKeyInCollection)
        : base(name)
    {
        mEnumerator = enumerator;
        mGetKey = getKey;
        mGetValue = getValue;

        mIsKeyInCollection = isKeyInCollection;
    }

И я хочу добавить удобную функцию для словарей (потому что они сами по себе эффективно поддерживают все операции).

Но проблема в том, что такой конструктор будет указывать только Key и Value, но не напрямую T, а T - это просто KeyValuePair. Есть ли способ указать компилятору для этого конструктора T KeyValuePair, например:

public DropDownControl<KeyValuePair<Key, Value>>(string name, IDictionary<Key, Value> dict) { ... }

В настоящее время я использую статическую функцию Create в качестве обходного пути, но мне нужен прямой конструктор.

public static DropDownControl<KeyValuePair<DKey, DValue>, DKey, DValue> Create<DKey, DValue>(string name, IDictionary<DKey, DValue> dictionary)
            where DKey: IComparable
        {
            return new DropDownControl<KeyValuePair<DKey, DValue>, DKey, DValue>(name, dictionary, kvp => kvp.Key, kvp => kvp.Value, key => dictionary.ContainsKey(key));
        }
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
0
20 250
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Нет, в основном. Статический метод в неуниверсальном классе (таком как DropDownControl [no <>]) - лучший подход, поскольку вы должны иметь возможность использовать определение типа при вызове Create (), т.е.

var control = DropDownControl.Create(name, dictionary);

C# 3.0 помогает здесь как с помощью «var» (здесь очень приветствуется), так и с помощью значительно улучшенных правил вывода общих типов. В некоторых (более общих) случаях другой аналогичный вариант - это метод расширения, но метод расширения для создания очень специфического элемента управления из словаря не кажется очень естественным - я бы использовал метод без расширения.

Что-то типа:

public static class DropDownControl
{
    public static DropDownControl<KeyValuePair<TKey,TValue>, TKey, TValue>
            Create<TKey,TValue>(IDictionary<TKey, TValue> value, string name)
    where TKey : IComparable
    {
        return new DropDownControl<KeyValuePair<TKey, TValue>, TKey, TValue>
            (name, value, pair => pair.Key, pair => pair.Value,
            key => value.ContainsKey(key)
        );
    }
}

Другой вариант - наследование, но оно мне не очень нравится ...

public class DropDownControl<TKey, TValue> :
    DropDownControl<KeyValuePair<TKey, TValue>, TKey, TValue>
    where TKey : IComparable
{
    public DropDownControl(IDictionary<TKey, TValue> lookup, string name)
        : base(name, lookup, pair => pair.Key, pair => pair.Value,
            key => lookup.ContainsKey(key)) { }
}

Это добавляет сложности и снижает вашу гибкость ... Я бы не стал этого делать ...

В целом, похоже, что вы, хочу, работаете только с IDictionary <,> - интересно, не можете ли вы упростить управление, чтобы просто использовать это, и заставить вызывающих лиц, не относящихся к словарю, обернуть себя фасадом IDictionary <,>?

Ну, я искал способ сделать что-то вроде частичной специализации шаблонов C++. Но, похоже, C# в настоящее время не может этого сделать (даже с помощью уловок).

Fionn 06.10.2008 12:37

Верно; дженерики не поддаются специализации шаблонов

Marc Gravell 07.10.2008 00:44

Если T всегда будет KeyValuePair<TKey,TValue>, то вовсе не обязательно, чтобы он был параметром универсального типа. Просто используйте фактический тип везде, где вы используете T.

В противном случае, если тип может иногда быть чем-то другим, я бы посоветовал вам, возможно, иметь базовый тип DropDownControl<TKey, TValue> : BaseControl с защищенным полем Helper того же типа и виртуальные реализации почти всех методов, которые просто вызывают свои аналоги на Helper; внутри него определите производный класс HeldAs<TPair>, который переопределяет все методы с «реальными» реализациями.

Конструктор DropDownControl<TKey,TValue> создаст новый экземпляр DropDownControl<TKey,TValue>.HeldAs<KeyValuePair<TKey,TValue>> и сохранит ссылку на него в Helper. Тогда внешний код может содержать ссылки типа DropDownControl<TKey,TValue> и использовать их, не зная и не заботясь о том, как хранятся ключи и значения. Код, который должен создать что-то, что хранит вещи по-другому и использует разные методы для извлечения ключей и значений, может вызывать конструктор DropDownControl<TKey,TValue>.HeldAs<actualStorageType>, передавая функции, которые могут преобразовывать actualStorageType в ключи или значения по мере необходимости.

Если ожидается, что какой-либо из методов DropDownControl<TKey,TValue> будет передавать this, тогда конструктор DropDownControl<TKey,TValue>.HeldAs<TStorage> должен установить Helper для себя, но конструктор базового типа после создания экземпляра производного типа должен установить ссылку Helper производного экземпляра на себя. (оболочка базового класса). Методы, которые будут передавать this, должны затем пройти Helper. Это гарантирует, что, когда экземпляр производного класса создается исключительно с целью обертывания, внешний мир никогда не получит ссылку на этот производный экземпляр, а вместо этого будет постоянно видеть оболочку.

Другие вопросы по теме