c# - tamaño - tipografia juvenil
¿Por qué no hay una tipografía implícita? (1)
Creo que entiendo por qué esta pequeña aplicación de consola C # no se compilará:
using System;
namespace ConsoleApp1
{
class Program
{
static void WriteFullName(Type t)
{
Console.WriteLine(t.FullName);
}
static void Main(string[] args)
{
WriteFullName(System.Text.Encoding);
}
}
}
El compilador genera un error CS0119: ''Encoding'' is a type which is not valid in the given context
. Sé que puedo producir un objeto Tipo a partir de su nombre usando el operador typeof()
:
...
static void Main(string[] args)
{
WriteFullName(typeof(System.Text.Encoding));
}
...
Y todo funciona como se espera.
Pero para mí, el uso de typeof()
siempre me ha parecido algo redundante. Si el compilador sabe que algún token es una referencia a un tipo dado (como sugiere el error CS0119) y sabe que el destino de alguna asignación (ya sea un parámetro de función, una variable o lo que sea) espera una referencia a un tipo dado, ¿por qué? ¿No puede el compilador tomarlo como una llamada implícita de typeof()
?
O tal vez el compilador es perfectamente capaz de dar ese paso, pero se ha elegido para no hacerlo debido a los problemas que pueden generar. ¿Resultaría en algún problema de ambigüedad / legibilidad que no puedo pensar en este momento?
Si el compilador sabe que algún token es una referencia a un tipo dado (como sugiere el error CS0119) y sabe que el destino de alguna asignación (ya sea un parámetro de función, una variable o lo que sea) espera una referencia a un tipo dado, ¿por qué? ¿No puede el compilador tomarlo como una llamada implícita de typeof ()?
En primer lugar, su propuesta es que el compilador razona tanto "adentro hacia afuera" como "afuera hacia adentro" al mismo tiempo . Es decir, para que su característica propuesta funcione, el compilador debe deducir que la expresión System.Text.Encoding
refiere a un tipo y que el contexto, una llamada a WriteFullName
, requiere un tipo. ¿Cómo sabemos que el contexto requiere un tipo ? La resolución de WriteFullName
requiere una resolución de sobrecarga porque podría haber cientos de ellos, y quizás solo uno de ellos tome un Type
como argumento en esa posición.
Así que ahora debemos diseñar una resolución de sobrecarga para reconocer este caso específico. La resolución de sobrecarga ya es bastante difícil. Ahora considera las implicaciones en la inferencia de tipos también.
C # está diseñado para que en la gran mayoría de los casos no tenga que hacer inferencia bidireccional porque la inferencia bidireccional es costosa y difícil . El lugar donde utilizamos la inferencia bidireccional es lambdas , y me tomó la mejor parte de un año implementarlo y probarlo. Obtener una inferencia sensible al contexto en las lambdas fue una característica clave que fue necesaria para hacer que LINQ funcione, por lo que valió la pena la carga extremadamente alta de tener una correcta inferencia bidireccional.
Además: ¿por qué es especial el Type
? Es perfectamente legal decir que el object x = typeof(T);
así que no debería object x = int;
¿Ser legal en tu propuesta? Supongamos que un tipo C
tiene una conversión implícita definida por el usuario de Type
a C
; no debería C c = string;
ser legal?
Pero dejemos eso de lado por un momento y consideremos los otros méritos de su propuesta. Por ejemplo, ¿qué propones hacer al respecto?
class C {
public static string FullName = "Hello";
}
...
Type c = C;
Console.WriteLine(c.FullName); // "C"
Console.WriteLine(C.FullName); // "Hello"
¿No le parece extraño que c == C
pero c.FullName != C.FullName
? Un principio básico del diseño del lenguaje de programación es que puede rellenar una expresión en una variable y el valor de la variable se comporta como la expresión , pero eso no es del todo cierto aquí.
Su propuesta es básicamente que cada expresión que se refiere a un tipo tiene un comportamiento diferente dependiendo de si se usa o asigna , y eso es muy confuso .
Ahora, podría decir, bueno, hagamos una sintaxis especial para desambiguar las situaciones en las que se usa el tipo de las situaciones donde se menciona el tipo, y existe una sintaxis de este tipo . ¡Es typeof(T)
! Si queremos tratar T.FullName
como T
es Type
, decimos typeof(T).FullName
y si queremos tratar T
como un calificador en una búsqueda, decimos T.FullName
, y ahora hemos desambiguado estos casos sin tener Para hacer cualquier inferencia bidireccional .
Básicamente, el problema fundamental es que los tipos no son de primera clase en C # . Hay cosas que puedes hacer con tipos que solo puedes hacer en tiempo de compilación. No hay:
Type t = b ? C : D;
List<t> l = new List<t>();
donde l
es List<C>
o List<D>
según el valor de b
. Dado que los tipos son expresiones muy especiales , y específicamente son expresiones que no tienen valor en el tiempo de ejecución , deben tener una sintaxis especial que indique cuándo se están utilizando como un valor.
Finalmente, también hay un argumento que se debe hacer acerca de la corrección probable. Si un desarrollador escribe Foo(Bar.Blah)
y Bar.Blah
es un tipo, las probabilidades son bastante buenas, han cometido un error y pensaron que Bar.Blah
era una expresión que se resuelve en un valor. Las probabilidades no son buenas de que intenten pasar un Type
a Foo
.
Siguiente pregunta:
¿Por qué es posible con grupos de métodos cuando se pasa a un argumento delegado? ¿Es porque el uso y la mención de un método son más fáciles de distinguir?
Los grupos de métodos no tienen miembros; nunca dices:
class C { public void M() {} }
...
var x = C.M.Whatever;
Porque CM
no tiene ningún miembro en absoluto. Entonces ese problema desaparece. Nunca decimos "bueno, CM
es convertible a Action
y Action
tiene un método Invoke
así que vamos a permitir que CMInvoke()
. Eso simplemente no sucede. Nuevamente, los grupos de métodos no son valores de primera clase . Solo después de que se conviertan a delegados convertirse en valores de primera clase.
Básicamente, los grupos de métodos se tratan como expresiones que tienen un valor pero ningún tipo , y luego las reglas de convertibilidad determinan qué grupos de métodos se pueden convertir a qué tipo de delegado.
Ahora, si fuera a presentar el argumento de que un grupo de métodos debería ser convertible implícitamente a MethodInfo
y usarse en cualquier contexto donde se esperaba un MethodInfo
, tendríamos que considerar los méritos de eso. Ha habido una propuesta durante décadas para hacer una infoof
operador (¡pronunciado "en-foof" por supuesto!) Que devolvería un MethodInfo
cuando se le diera un grupo de métodos y un PropertyInfo
cuando se le diera una propiedad y así sucesivamente, y esa propuesta siempre ha fallado Como demasiado trabajo de diseño por muy poco beneficio. nameof
fue la versión barata de implementar que se realizó.
Una pregunta que no hiciste pero que parece pertinente:
Usted dijo que
C.FullName
podría ser ambiguo porque no estaría claro siC
es unType
o el tipoC
¿Hay otras ambigüedades similares en C #?
¡Sí! Considerar:
enum Color { Red }
class C {
public Color Color { get; private set; }
public void M(Color c) { }
public void N(String s) { }
public void O() {
M(Color.Red);
N(Color.ToString());
}
}
En este escenario, llamado hábilmente "Color Color Problem", el compilador de C # se las arregla para descubrir que Color
en la llamada a M
significa el tipo , y que en la llamada a N
, significa this.Color
. this.Color
. Realice una búsqueda en la especificación de "Color Color" y encontrará la regla, o vea la publicación del blog Color Color .