Existe uma abordagem razoável para parâmetros de tipo "padrão" em genéricos C #?

91

Em modelos C ++, pode-se especificar que um determinado parâmetro de tipo é um padrão. Ou seja, a menos que especificado explicitamente, ele usará o tipo T.

Isso pode ser feito ou aproximado em C #?

Estou procurando algo como:

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

Para que uma instância do tipo que não especifica explicitamente T2:

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

Seria essencialmente:

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

Em última análise, estou olhando para um caso em que há um modelo que é amplamente usado, mas estou pensando em expandir com um parâmetro de tipo adicional. Eu poderia fazer uma subclasse, eu acho, mas estava curioso se havia outras opções nessa linha.

el2iot2
fonte

Respostas:

76

Subclassificar é a melhor opção.

Eu criaria uma subclasse de sua classe genérica principal:

class BaseGeneric<T,U>

com uma classe específica

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

Isso torna mais fácil manter sua lógica em um só lugar (a classe base), mas também fornece ambas as opções de uso. Dependendo da classe, provavelmente há muito pouco trabalho extra para fazer isso acontecer.

Reed Copsey
fonte
1
ah ... isso faz sentido. O nome do tipo pode ser o mesmo se os parâmetros do tipo fornecerem uma assinatura exclusiva?
el2iot2
2
@ee: sim, os genéricos são overloadablepor contagem de parâmetro.
Mehrdad Afshari
@ee: Sim, mas eu teria medo de fazer isso. É "legal" em .NET fazer isso, mas pode causar confusão. Eu preferiria que o nome do tipo derivado de string fosse semelhante ao da classe genérica principal (portanto, é óbvio o que é / fácil de encontrar), mas um nome que torne óbvio que é uma string.
Reed Copsey,
@Reed: Isso realmente causa confusão? Para este caso específico, acho que ter o mesmo nome até ajuda. Existem exemplos em .NET que fazem a mesma coisa: Func <> delegate por exemplo.
Mehrdad Afshari,
4
Por exemplo, Predicate <T> é apenas Func <T, bool>, mas renomeado porque tem um propósito diferente.
Reed Copsey,
19

Uma solução é a subclasse. Outro que eu usaria, são os métodos de fábrica (combinados com a palavra-chave 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>();

No exemplo acima val2é do tipo MyTemplate<int,string> e não um tipo derivado dele.

Um tipo class MyStringTemplate<T>:MyTemplate<T,string>não é igual a MyTemplate<T,string>. Isso pode representar alguns problemas em determinados cenários. Por exemplo, você não pode lançar uma instância de MyTemplate<T,string>para MyStringTemplate<T>.

Thanasis Ioannidis
fonte
3
Esta é a abordagem mais utilizável. Solução muito boa
T-moty
12

você também pode criar uma classe Overload como esta

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

public class MyTemplate<T1> : MyTemplate<T1, string>{}
Nerdroid
fonte
Leia outras respostas antes de postar uma resposta atrasada, porque provavelmente sua solução é a mesma que as outras.
Cheng Chen
7
a resposta aceita foi criar uma classe com um nome diferente, minha solução é Overloading the same class
Nerdroid
2
Não, ambos estão criando uma nova classe. O nome não importa aqui. MyTemplate<T1>não é uma classe diferente de MyTemplate<T1, T2>nenhuma delas AnotherTemplate<T1>.
Cheng Chen
7
Mesmo que os tipos reais sejam diferentes, o que é claro e correto, a codificação fica mais fácil mantendo o mesmo nome. Não é óbvio para todos que a "sobrecarga" de classe pode ser usada dessa forma. @DannyChen está certo - do ponto de vista técnico os resultados são os mesmos, mas esta resposta está mais perto de alcançar o que o OP pediu.
Kuba
1
Infelizmente, esta solução ainda é inferior aos verdadeiros argumentos genéricos "padrão", porque a MyTemplate<T1, string>não pode ser atribuído MyTemplate<T1>, o que pode ser desejável
Felk
9

C # não oferece suporte a esse recurso.

Como você disse, você pode criar uma subclasse (se não estiver lacrada e duplicar todas as declarações do construtor), mas é uma coisa completamente diferente.

Mehrdad Afshari
fonte
2

Infelizmente, C # não oferece suporte ao que você está tentando fazer. Seria um recurso difícil de implementar, dado que o tipo padrão de um parâmetro teria que aderir às restrições genéricas e provavelmente criaria dores de cabeça quando o CLR tentasse garantir a segurança do tipo.

Andrew Hare
fonte
1
Na verdade não. Isso poderia ter sido feito com um atributo (como parâmetros padrão em VB.NET) e ter o compilador substituindo-o em tempo de compilação. O principal motivo são os objetivos de design do C #.
Mehrdad Afshari,
O compilador deve garantir que o parâmetro padrão satisfaça as restrições genéricas. Além disso, o parâmetro padrão seria uma restrição genérica em si, porque quaisquer suposições feitas sobre o parâmetro de tipo no método exigiriam que qualquer parâmetro de tipo não padrão herdasse dele.
Andrew Hare,
@Andrew, o parâmetro padrão não precisa ser uma restrição genérica. Se isso se comportasse mais como parâmetros de modelo padrão em C ++ do que estendendo sobre a classe de automatonic, seria perfeitamente adequado fazer: MyTemplate <int, float> x = null Porque T2 não tem restrições genéricas, então float é bom, apesar do tipo padrão de string . Desta forma, os parâmetros do modelo padrão são essencialmente apenas "açúcar sintático" para escrever MyTemplate <int> como uma abreviação para MyTemplate <int, string>.
Tyler Laing
@Andrew, concordo que o compilador C # deve verificar se esses parâmetros de modelo padrão satisfazem as restrições genéricas existentes. O compilador C # já verifica algo semelhante em declarações de classe, por exemplo, adicione uma restrição genérica como "where U: ISomeInterface" à classe BaseGeneric <T, U> de Reed e então MyGeneric <T> falhará ao compilar com um erro. Verificar um parâmetro de modelo padrão seria praticamente o mesmo: o compilador verifica a declaração de uma classe e a mensagem de erro pode ser a mesma ou muito semelhante.
Tyler Laing
"Seria um recurso difícil de implementar" Isso é um absurdo. Seria trivial de implementar. O trabalho de validação de um tipo em relação às restrições do modelo não fica mais difícil porque o tipo de candidato aparece em um lugar diferente no arquivo (ou seja, no modelo em vez de na instanciação do modelo).
Mud