¿Existe un enfoque razonable para los parámetros de tipo "predeterminados" en C # Generics?

91

En las plantillas de C ++, se puede especificar que un determinado parámetro de tipo sea predeterminado. Es decir, a menos que se especifique explícitamente, utilizará el tipo T.

¿Se puede hacer esto o aproximarlo en C #?

Estoy buscando algo como:

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

De modo que una instancia del tipo que no especifica explícitamente T2:

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

Sería esencialmente:

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

En última instancia, estoy viendo un caso en el que hay una plantilla que se usa bastante ampliamente, pero estoy considerando expandirme con un parámetro de tipo adicional. Podría subclase, supongo, pero tenía curiosidad por saber si había otras opciones en este sentido.

el2iot2
fuente

Respuestas:

76

La subclasificación es la mejor opción.

Subclasearía su clase genérica principal:

class BaseGeneric<T,U>

con una clase específica

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

Esto hace que sea fácil mantener su lógica en un solo lugar (la clase base), pero también fácil proporcionar ambas opciones de uso. Dependiendo de la clase, probablemente se necesite muy poco trabajo adicional para que esto suceda.

Reed Copsey
fuente
1
ah ... eso tiene sentido. ¿Se permite que el nombre del tipo sea el mismo si los parámetros del tipo proporcionan una firma única?
el2iot2
2
@ee: sí, los genéricos son overloadablepor recuento de parámetros.
Mehrdad Afshari
@ee: Sí, pero no me atrevería a hacer eso. Es "legal" en .NET hacerlo, pero puede generar confusión. Preferiría que el nombre del tipo derivado de la cadena sea similar a la clase genérica principal (por lo que es obvio qué es / fácil de encontrar), pero un nombre que haga obvio que es una cadena.
Reed Copsey
@Reed: ¿Realmente genera confusión? Para este caso específico, creo que tener el mismo nombre incluso ayuda. Hay ejemplos en .NET que hacen lo mismo: Func <> delegate, por ejemplo.
Mehrdad Afshari
4
Por ejemplo, Predicate <T> es simplemente Func <T, bool>, pero se le cambia el nombre porque tiene un propósito diferente.
Reed Copsey
19

Una solución es la subclasificación. Otro que usaría en su lugar son los métodos de fábrica (combinados con la palabra clave 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>();

En el ejemplo anterior val2es de tipo MyTemplate<int,string> y no derivado de él.

Un tipo class MyStringTemplate<T>:MyTemplate<T,string>no es del mismo tipo que MyTemplate<T,string>. Esto podría plantear algunos problemas en determinados escenarios. Por ejemplo, no puedes lanzar una instancia de MyTemplate<T,string>to MyStringTemplate<T>.

Thanasis Ioannidis
fuente
3
Este es el enfoque más útil. Muy buena solución
T-moty
12

también puedes crear una clase Overload así

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

public class MyTemplate<T1> : MyTemplate<T1, string>{}
Nerdroid
fuente
Lea otras respuestas antes de publicar una respuesta tardía, porque probablemente su solución sea la misma que otras.
Cheng Chen
7
la respuesta aceptada fue crear una clase con un nombre diferente, mi solución es sobrecargar la misma clase
Nerdroid
2
No, ambos están creando una nueva clase. El nombre no importa aquí. MyTemplate<T1>es una clase diferente de MyTemplate<T1, T2>ninguna AnotherTemplate<T1>.
Cheng Chen
7
Incluso si los tipos reales son diferentes, lo cual es claro y correcto, la codificación es más fácil si mantiene el mismo nombre. No es obvio para todos que la clase "sobrecarga" pueda usarse de esa manera. @DannyChen tiene razón: desde el punto de vista técnico, los resultados son los mismos, pero esta respuesta está más cerca de lograr lo que OP pidió.
Kuba
1
Desafortunadamente, esta solución sigue siendo inferior a los verdaderos argumentos genéricos "predeterminados", porque MyTemplate<T1, string>no se puede asignar a MyTemplate<T1>, lo que puede ser deseable
Felk
9

C # no admite dicha función.

Como dijiste, puedes subclasificarlo (si no está sellado y duplicar todas las declaraciones del constructor) pero es algo completamente diferente.

Mehrdad Afshari
fuente
2

Desafortunadamente, C # no es compatible con lo que está intentando hacer. Sería una característica difícil de implementar dado que el tipo predeterminado para un parámetro tendría que adherirse a las restricciones genéricas y probablemente crearía dolores de cabeza cuando el CLR intentara garantizar la seguridad de tipos.

Andrew Hare
fuente
1
Realmente no. Podría haberse hecho con un atributo (como los parámetros predeterminados en VB.NET) y hacer que el compilador lo reemplace en el momento de la compilación. La razón principal son los objetivos de diseño de C #.
Mehrdad Afshari
El compilador debe asegurarse de que el parámetro predeterminado satisfaga las restricciones genéricas. Además, el parámetro predeterminado sería una restricción genérica en sí misma porque cualquier suposición sobre el parámetro de tipo en el método requeriría que cualquier parámetro de tipo no predeterminado heredara de él.
Andrew Hare
@Andrew, el parámetro predeterminado no necesita ser una restricción genérica. Si esto se comportara más como parámetros de plantilla predeterminados en C ++, luego extendiéndose a la clase de automatonic, estaría perfectamente bien hacerlo: MyTemplate <int, float> x = null Porque T2 no tiene restricciones genéricas, por lo que float está bien a pesar del tipo predeterminado de cadena . De esta manera, los parámetros de plantilla predeterminados son esencialmente "azúcar sintáctico" para escribir MyTemplate <int> como abreviatura de MyTemplate <int, string>.
Tyler Laing
@ Andrew, estoy de acuerdo en que el compilador de C # debería verificar que dichos parámetros de plantilla predeterminados satisfagan las constantes genéricas existentes. El compilador de C # ya verifica algo similar en las declaraciones de clase, por ejemplo, agregue una restricción genérica como "donde U: ISomeInterface" a la clase BaseGeneric <T, U> de Reed y luego MyGeneric <T> fallará al compilar con un error. Verificar un parámetro de plantilla predeterminado sería muy parecido: el compilador verifica la declaración de una clase y el mensaje de error puede ser el mismo o muy similar.
Tyler Laing
"Sería una característica difícil de implementar" Eso es una tontería. Sería trivial de implementar. El trabajo de validar un tipo contra las restricciones de la plantilla no se vuelve más difícil porque el tipo candidato aparece en un lugar diferente en el archivo (es decir, en la plantilla en lugar de en la instanciación de la plantilla).
Barro