Есть ли разумный подход к параметрам типа «по умолчанию» в C # Generics?

93

В шаблонах C ++ можно указать, что определенный параметр типа является значением по умолчанию. Т.е., если явно не указано иное, будет использоваться тип T.

Можно ли это сделать или приблизить на C #?

Я ищу что-то вроде:

public class MyTemplate<T1, T2=string> {}

Таким образом, экземпляр типа, который явно не указывает T2:

MyTemplate<int> t = new MyTemplate<int>();

Было бы по существу:

MyTemplate<int, string> t = new MyTemplate<int, string>();

В конечном итоге я рассматриваю случай, когда существует довольно широко используемый шаблон, но я рассматриваю возможность расширения с помощью дополнительного параметра типа. Думаю, я мог бы создать подкласс, но мне было любопытно, есть ли другие варианты в этом ключе.

el2iot2
источник

Ответы:

77

Подклассификация - лучший вариант.

Я бы подклассифицировал ваш основной общий класс:

class BaseGeneric<T,U>

с определенным классом

class MyGeneric<T> : BaseGeneric<T, string>

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

Рид Копси
источник
1
ах ... это имеет смысл. Разрешено ли имя типа быть таким же, если параметры типа предоставляют уникальную подпись?
el2iot2 01
2
@ee: да, дженерики - overloadableпо количеству параметров.
Мехрдад Афшари, 01
@ee: Да, но я бы с осторожностью поступил так. В .NET это «законно», но это может привести к путанице. Я бы предпочел, чтобы имя типа, производного от строки, было похоже на основной универсальный класс (чтобы было очевидно, что это / легко найти), но имя, которое делает очевидным, что это строка.
Рид Копси
@ Рид: Это действительно приводит к путанице? В этом конкретном случае, я думаю, даже помогает одно и то же имя. В .NET есть примеры, которые делают то же самое: например, делегат Func <>.
Mehrdad Afshari 02
4
Например, Predicate <T> - это просто Func <T, bool>, но переименовано, поскольку оно предназначено для другой цели.
Рид Копси
19

Одно из решений - создание подклассов. Другой, который я бы использовал вместо этого, - это фабричные методы (в сочетании с ключевым словом var).

public class MyTemplate<T1,T2>
{
     public MyTemplate(..args..) { ... } // constructor
}

public static class MyTemplate{

     public static MyTemplate<T1,T2> Create<T1,T2>(..args..)
     {
         return new MyTemplate<T1, T2>(... params ...);
     }

     public static MyTemplate<T1, string> Create<T1>(...args...)
     {
         return new MyTemplate<T1, string>(... params ...);
     }
}

var val1 = MyTemplate.Create<int,decimal>();
var val2 = MyTemplate.Create<int>();

В приведенном выше примере val2это тип, MyTemplate<int,string> а не производный от него тип.

Тип class MyStringTemplate<T>:MyTemplate<T,string>- это не тот же тип, что и MyTemplate<T,string>. В определенных сценариях это может вызвать некоторые проблемы. Например, вы не можете привести экземпляр MyTemplate<T,string>кMyStringTemplate<T> .

Танасис Иоаннидис
источник
3
Это наиболее удобный подход. Очень красивое решение
T-moty 04
12

вы также можете создать класс Overload следующим образом

public class MyTemplate<T1, T2> {
    public T1 Prop1 { get; set; }
    public T2 Prop2 { get; set; }
}

public class MyTemplate<T1> : MyTemplate<T1, string>{}
Nerdroid
источник
Пожалуйста, прочтите другие ответы, прежде чем публиковать поздний ответ, потому что, вероятно, ваше решение такое же, как и другие.
Ченг Чен
7
принятый ответ создавал класс с другим именем, мое решение - перегрузка того же класса
Nerdroid
2
Нет, оба создают новый класс. Имя здесь не имеет значения. MyTemplate<T1>это другой класс MyTemplate<T1, T2>, чем ни то, ни другое AnotherTemplate<T1>.
Ченг Чен
7
Даже если реальные типы разные, что ясно и правильно, кодирование будет проще, если вы сохраните то же имя. Не для всех очевидно, что таким образом можно использовать «перегрузку» класса. @DannyChen прав - с технической точки зрения результаты такие же, но этот ответ ближе к достижению того, чего просил OP.
Куба
1
К сожалению, это решение по-прежнему уступает истинным универсальным аргументам "по умолчанию", потому что a MyTemplate<T1, string>не может быть назначено MyTemplate<T1>, что может быть
желательно
10

C # не поддерживает такую ​​функцию.

Как вы сказали, вы можете создать его подкласс (если он не запечатан, и продублировать все объявления конструктора), но это совершенно другое дело.

Мехрдад Афшари
источник
3

К сожалению, C # не поддерживает то, что вы пытаетесь сделать. Эту функцию было бы сложно реализовать, учитывая, что тип по умолчанию для параметра должен соответствовать общим ограничениям и, скорее всего, вызовет головную боль, когда среда CLR попытается обеспечить безопасность типов.

Эндрю Хэйр
источник
1
На самом деле, нет. Это можно было сделать с помощью атрибута (например, параметров по умолчанию в VB.NET), и компилятор заменил его во время компиляции. Основная причина - цели дизайна C #.
Мехрдад Афшари, 01
Компилятор должен гарантировать, что параметр по умолчанию удовлетворяет общим ограничениям. Кроме того, параметр по умолчанию сам по себе будет общим ограничением, поскольку любые предположения, сделанные в отношении параметра типа в методе, потребуют, чтобы от него унаследовался любой параметр типа, не являющийся параметром по умолчанию.
Эндрю Хэйр
@Andrew, параметр по умолчанию не обязательно должен быть общим ограничением. Если бы это было больше похоже на параметры шаблона по умолчанию в C ++, а затем расширялось бы на класс Automatonic, было бы прекрасно: MyTemplate <int, float> x = null Поскольку T2 не имеет общих ограничений, поэтому float в порядке, несмотря на тип строки по умолчанию . Таким образом, параметры шаблона по умолчанию представляют собой просто «синтаксический сахар» для записи MyTemplate <int> как сокращения MyTemplate <int, string>.
Тайлер Лэйнг,
@Andrew, я согласен с тем, что компилятор C # должен проверять, удовлетворяют ли такие параметры шаблона по умолчанию существующие общие константы. Компилятор C # уже проверяет что-то похожее в объявлениях классов, например, добавляет общее ограничение, такое как «where U: ISomeInterface», в класс Reed BaseGeneric <T, U>, и тогда MyGeneric <T> не сможет скомпилировать с ошибкой. Проверка параметра шаблона по умолчанию будет во многом такой же: компилятор проверяет объявление класса, и сообщение об ошибке может быть таким же или очень похожим.
Тайлер Лэйнг,
«Было бы сложно реализовать эту функцию» Это ерунда. Реализовать было бы тривиально. Работа по проверке типа на соответствие ограничениям шаблона не усложняется, потому что тип-кандидат появляется в другом месте в файле (то есть в шаблоне, а не при создании экземпляра шаблона).
Mud