¿Por qué hay una nueva restricción () en C # pero no hay otra restricción similar?

19

En genéricos de C #, podemos declarar una restricción para que un parámetro de tipo Ttenga un constructor predeterminado, diciendo where T : new(). Sin embargo, ningún otro tipo de restricciones como esta son válidas, new(string)por ejemplo, etc.

Desde una perspectiva de diseño y / o implementación de lenguaje, ¿cuál es la razón de esto?

¿Hay algo en la forma en que funcionan los constructores o en la forma en que se implementa el sistema de tipos que prohíbe esto (o al menos lo hace más difícil)? Si es así, ¿qué es? Recuerdo haber leído en algún lugar que default(T)realmente compila new T()para T : struct. ¿Está relacionado con esto, tal vez?

¿O es simplemente una decisión de diseño tomada para evitar complicar demasiado el lenguaje?

Theodoros Chatzigiannakis
fuente
new(string)no es una restricción de constructor predeterminada. Su pregunta equivale a decir "¿Por qué no hay restricciones que requieran firmas de constructor específicas?" Muy probablemente porque tal restricción no sería particularmente útil.
Robert Harvey
3
@RobertHarvey Sí, es exactamente lo que digo. Básicamente, me pregunto si hay algo que haga que los constructores predeterminados sean especiales en cuanto a la implementación o si fue solo una elección arbitraria incluir esto y no el otro.
Theodoros Chatzigiannakis
Los constructores predeterminados son útiles de ciertas maneras específicas e importantes. Por ejemplo, hacen que un tipo sea fácilmente serializable.
Robert Harvey
66
Las firmas de ctor específicas podrían ser útiles. Por ejemplo, si su variable de tipo está restringida a ser de Colección <T> con un factor de T (Colección <T>), sabe que puede construir nuevas colecciones a partir de otra. Sin embargo, si esa utilidad vale la complejidad adicional, es una pregunta.
Phoshi

Respuestas:

5

Para una respuesta autorizada, lo remitiría a la respuesta de Eric Lippert a esa pregunta en StackOverflow hace unos años, un fragmento del cual se cita brevemente a continuación.

Sin embargo, en este caso específico, ciertamente puedo darle algunas razones por las que rechazaría la función si apareciera en una reunión de diseño como una posible característica para una futura versión del lenguaje.

...

Digo que deberíamos hacer toda la función o no hacerlo en absoluto. Si es importante poder restringir los tipos para tener constructores particulares, hagamos toda la función y restrinja los tipos en función de los miembros en general y no solo de los constructores.

Chris Hannon
fuente
2
Esa cita, tomada fuera de contexto, es muy engañosa. Sugiere que Eric pensó que Microsoft debería haber "ido todo el camino", lo cual no es su posición en absoluto. Él está, de hecho, directamente en contra de la característica propuesta.
Robert Harvey
1
Gracias, esto responde parcialmente a mi pregunta: cerca del final de su respuesta, Eric Lippert menciona que es una limitación de la IL e incluir esta característica requeriría una adición a la IL. Sería perfecto si pudiera proporcionar una fuente sobre qué IL se genera para la parte que se implementa, es decir, llamar genéricamente a un constructor predeterminado.
Theodoros Chatzigiannakis
@TheodorosChatzigiannakis: ¿Por qué no enciendes el Descompilador de Telerik y lo descubres por ti mismo?
Robert Harvey
2
@RobertHarvey No creo que sugiera una posición para él, pero he incluido una cita adicional para enfatizar su posición.
Chris Hannon
1
Hmm, F # tiene formas más avanzadas de restringir un tipo , como verificar como tiempo de compilación si una clase tiene un operador. Quizás, F # necesita un sistema de restricción más poderoso que C # debido a su estricto control de tipo. Sin embargo, este lenguaje puede implementar formas avanzadas para mantener la clase en .Net Framework.
OnesimusUnbound
16

La descomposición (según la sugerencia de Robert Harvey) produjo lo siguiente, para cualquier persona interesada. Este método:

static T GenericMake<T>()
    where T : new()
{
    return new T();
}

Aparentemente, cuando se compila, se convierte en esto:

private static T GenericMake<T>()
    where T : new()
{
    T t;
    T t1 = default(T);
    if (t1 == null)
    {
        t = Activator.CreateInstance<T>();
    }
    else
    {
        t1 = default(T);
        t = t1;
    }
    return t;
}
  • Si Tes un tipo de valor,new() convierte en default(T).
  • Si Tes un tipo de referencia, new()funciona utilizando la reflexión. Activator.CreateInstance()Llamadas internas RuntimeType.CreateInstanceDefaultCtor().

Así que ahí está: internamente, los constructores predeterminados son realmente especiales para C # en relación con el CLR. Dar a otros constructores el mismo tratamiento hubiera sido costoso, incluso si hay algunos casos de uso válidos para restricciones más complicadas en genéricos.

Theodoros Chatzigiannakis
fuente
44
Interesante. ¿Por qué la llamada duplicada a los default(T) tipos de valor?
Avner Shahar-Kashtan
2
@ AvnerShahar-Kashtan No lo sé. Puede ser un artefacto del proceso de compilación / descompilación, como la tvariable (que podría reemplazarse por returndeclaraciones anidadas ).
Theodoros Chatzigiannakis