Por qué C # no permite funciones no miembro como C ++

C # no permitirá escribir funciones que no sean miembros y cada método debe ser parte de una clase. Estaba pensando en esto como una restricción en todos los lenguajes CLI. Pero me equivoqué y descubrí que C ++ / CLI admite funciones que no son miembros. Cuando se comstack, el comstackdor convertirá el método en miembro de alguna clase sin nombre.

Esto es lo que dice el estándar C ++ / CLI:

[Nota: las funciones que no son miembros son tratadas por la CLI como miembros de alguna clase sin nombre; sin embargo, en el código fuente de C ++ / CLI, tales funciones no pueden ser calificadas explícitamente con ese nombre de clase. nota final]

La encoding de funciones no miembro en metadatos no está especificada. [Nota: Esto no causa problemas de interoperabilidad porque dichas funciones no pueden tener visibilidad pública. nota final]

Entonces mi pregunta es por qué C no implementa algo como esto? ¿O crees que no debería haber funciones no miembro y que cada método debería pertenecer a alguna clase?

Mi opinión es tener soporte para funciones no miembro y ayuda a evitar la contaminación de la interfaz de la clase.

Alguna idea..?

Ver la publicación de este blog:

http://blogs.msdn.com/ericlippert/archive/2009/06/22/why-doesn-tc-implement-top-level-methods.aspx

(…)

Me preguntan “¿por qué C # no implementa la función X?” todo el tiempo. La respuesta es siempre la misma: porque nadie diseñó, especificó, implementó, probó, documentó y envió esa característica. Las seis cosas son necesarias para que una característica suceda. Todos ellos cuestan grandes cantidades de tiempo, esfuerzo y dinero. Las características no son baratas, y hacemos todo lo posible para asegurarnos de que solo estamos enviando aquellas características que ofrecen los mejores beneficios posibles para nuestros usuarios, dado nuestro tiempo limitado, nuestro esfuerzo y nuestros presupuestos monetarios.

Entiendo que una respuesta tan general probablemente no aborde la pregunta específica.

En este caso particular, el claro beneficio para el usuario en el pasado no era lo suficientemente grande como para justificar las complicaciones del lenguaje que se produciría. Al limitar la forma en que las diferentes entidades lingüísticas se anidan entre sí, (1) restringimos los progtwigs legales a un estilo común y fácil de entender, y (2) hacemos posible definir reglas de “búsqueda de identificador” que sean comprensibles, especificables, implementables y comprobables y documentable.

Al restringir que los cuerpos de métodos siempre estén dentro de una estructura o clase, hacemos más fácil razonar sobre el significado de un identificador no calificado utilizado en un contexto de invocación; tal cosa es siempre un miembro invocable del tipo actual (o un tipo base).

(…)

y esta publicación de seguimiento:

http://blogs.msdn.com/ericlippert/archive/2009/06/24/it-already-is-a-scripting-language.aspx

(…)

Al igual que todas las decisiones de diseño, cuando nos enfrentamos con una serie de ideas competitivas, convincentes, valiosas y no comparables, debemos encontrar un compromiso factible. No hacemos eso excepto considerando todas las posibilidades , que es lo que estamos haciendo en este caso.

(énfasis del texto original)

C # no lo permite porque Java no lo permitió.

Puedo pensar en varias razones por las cuales los diseñadores de Java probablemente no lo permitieron

  • Java fue diseñado para ser simple. Intentaron crear un idioma sin atajos aleatorios, por lo que generalmente solo tiene una forma simple de hacer todo, incluso si otros enfoques hubieran sido más claros o más concisos. Querían minimizar la curva de aprendizaje, y aprender “una clase puede contener métodos” es más simple que “una clase puede contener métodos, y las funciones pueden existir fuera de las clases”.
  • Superficialmente, parece menos orientado a objetos. (Todo lo que no es parte de un objeto, obviamente, no puede estar orientado a objetos? ¿Por supuesto? C ++ dice que sí, pero C ++ no participó en esta decisión)

Como ya dije en los comentarios, creo que esta es una buena pregunta, y hay muchos casos en los que las funciones de no miembros hubieran sido preferibles. (Esta parte es principalmente una respuesta a todas las demás respuestas que dicen “no lo necesita”)

En C ++, donde las funciones no miembro están permitidas, a menudo son preferidas, por varias razones:

  • Ayuda a la encapsulación. Cuantos menos métodos tengan acceso a los miembros privados de una clase, más fácil será la clase para refactorizar o mantener. La encapsulación es una parte importante de OOP.
  • El código se puede reutilizar mucho más fácilmente cuando no es parte de una clase. Por ejemplo, la biblioteca estándar de C ++ define std::find o std :: sort` como funciones no miembro, por lo que se pueden reutilizar en cualquier tipo de secuencias, ya sean matrices, conjuntos, listas vinculadas o (para std: : encontrar, al menos) transmisiones. La reutilización de código también es una parte importante de OOP.
  • Nos da un mejor desacoplamiento. La función de find no necesita conocer la clase LinkedList para poder trabajar en ella. Si se hubiera definido como una función miembro, sería un miembro de la clase LinkedList, básicamente fusionando los dos conceptos en un gran blob.
  • Extensibilidad. Si acepta que la interfaz de una clase no es solo “todos sus miembros públicos”, sino también “todas las funciones que no son miembros que operan en la clase”, entonces es posible ampliar la interfaz de una clase sin tener que editar o incluso recomstackr la clase en sí.

La capacidad de tener funciones no miembro puede haberse originado con C (donde no tenías otra opción), pero en C ++ moderno, es una característica vital en sí misma, no solo por propósitos de comparaciones hacia atrás, sino debido a la simplicidad , más limpio y más código reutilizable que permite.

De hecho, C # parece haberse dado cuenta de las mismas cosas, mucho después. ¿Por qué crees que se agregaron los métodos de extensión? Son un bash de lograr lo anterior, mientras se preserva la syntax simple similar a Java. Las lambdas también son ejemplos interesantes, ya que también son esencialmente pequeñas funciones definidas libremente, no como miembros de una clase en particular. Así que sí, el concepto de funciones no miembro es útil, y los diseñadores de C # se han dado cuenta de lo mismo. Acaban de intentar infiltrar el concepto por la puerta de atrás.

http://www.ddj.com/cpp/184401197 y http://www.gotw.ca/publications/mill02.htm son dos artículos escritos por expertos en C ++ sobre el tema.

Las funciones no miembro son buenas porque mejoran la encapsulación y reducen el acoplamiento entre tipos. La mayoría de los lenguajes de progtwigción modernos, como Haskell y F #, admiten funciones gratuitas.

¿Cuál es el beneficio de no poner cada método en una clase con nombre? ¿Por qué una función no miembro “contaminaría” la interfaz de la clase? Si no lo quiere como parte de la API pública de una clase, tampoco lo haga público o no lo incluya en esa clase. Siempre puedes crear una clase diferente.

No recuerdo haber querido escribir un método flotando sin scope apropiado, aparte de las funciones anónimas, por supuesto (que no son las mismas).

En resumen, no puedo ver ningún beneficio en las funciones que no son miembros, pero puedo ver los beneficios en términos de consistencia, nombres y documentación al poner todos los métodos en una clase apropiadamente nombrada.

La CLS (especificación de lenguaje común) dice que no debe tener funciones no miembro en una biblioteca que se ajuste a la CLS. Es como un conjunto adicional de restricciones además de las restricciones básicas de la CLI (interfaz de lenguaje común).

Es posible que una versión futura de C # agregue la capacidad de escribir una directiva using que permita acceder a los miembros estáticos de una clase sin la calificación del nombre de clase:

 using System.Linq.Enumerable; // Enumerable is a static class ... IEnumerable range = Range(1, 10); // finds Enumerable.Range 

Entonces no habrá necesidad de cambiar el CLS y las bibliotecas existentes.

Estas publicaciones de blog demuestran una biblioteca para progtwigción funcional en C #, y usan un nombre de clase que tiene solo una letra de longitud, para tratar de reducir el ruido causado por el requisito de calificar llamadas a métodos estáticos. Ejemplos como ese serían un poco más agradables si las directivas de using pudieran apuntar a las clases.

  • Tener todo el código dentro de las clases permite un conjunto más poderoso de capacidades de reflexión.
  • Permite el uso de intializadores estáticos, que pueden inicializar los datos necesarios mediante métodos estáticos dentro de una clase.
  • Evita los conflictos de nombres entre métodos al encerrarlos explícitamente dentro de una unidad que no puede ser agregada por otra unidad de comstackción.

Desde Java, la mayoría de los progtwigdores han aceptado fácilmente que cualquier método es miembro de una clase. No hago ningún obstáculo considerable y hago que el concepto de método sea más estrecho, lo que hace que un idioma sea más fácil.

Sin embargo, de hecho, la clase infiere el objeto y el objeto infiere el estado, por lo que el concepto de clase que contiene solo métodos estáticos parece un poco absurdo.

Creo que realmente necesita aclarar para qué quiere crear métodos estáticos no miembros.

Por ejemplo, algunas de las cosas que podría desear podrían manejarse con los métodos de extensión

Otro uso típico (de una clase que solo contiene métodos estáticos) está en una biblioteca. En este caso, hay poco daño al crear una clase en un ensamblaje que está completamente compuesto de métodos estáticos. Los mantiene juntos, evita nombrar colisiones. Después de todo, hay métodos estáticos en matemáticas que sirven para el mismo propósito.

Además, no necesariamente debe comparar el modelo de objetos de C ++ con C #. C ++ es en gran parte (pero no perfectamente) compatible con C, que no tenía ningún sistema de clase, por lo que C ++ tuvo que admitir esta expresión de progtwigción fuera del legado de C, no para ningún imperativo de diseño en particular.

Tenga algo en cuenta: C ++ es un lenguaje mucho más complicado que C #. Y a pesar de que pueden ser similares sintácticamente, son bestias muy diferentes semánticamente. No pensarías que sería terriblemente difícil hacer un cambio como este, pero podría ver cómo podría ser. ANTLR tiene una buena página wiki llamada ¿Qué hace que un problema de lenguaje sea difícil? eso es bueno consultar para preguntas como esta. En este caso:

¿Lexer sensible al contexto? No puede decidir qué símbolo de vocabulario coincidirá a menos que sepa qué tipo de oración está analizando.

Ahora, en lugar de solo preocuparnos por las funciones definidas en las clases, tenemos que preocuparnos por las funciones definidas fuera de las clases. Conceptualmente, no hay mucha diferencia. Pero en términos de leer y analizar el código, ahora tiene el problema adicional de tener que decir “si una función está fuera de una clase, pertenece a esta clase sin nombre. Sin embargo, si está dentro de la clase, entonces pertenece a esa clase. clase.”

Además, si el comstackdor encuentra un método como este:

 public void Foo() { Bar(); } 

… ahora tiene que responder a la pregunta “¿Está el Bar ubicado dentro de esta clase o es una clase global?”

¿Referencias futuras o externas? Es decir, ¿se necesitan pases múltiples? Pascal tiene una referencia “hacia adelante” para manejar referencias de procedimientos dentro del archivo, pero las referencias a los procedimientos en otros archivos a través de las cláusulas USES, etc … requieren un manejo especial.

Esto es otra cosa que causa problemas. Recuerde que C # no requiere declaraciones futuras. El comstackdor realizará una pasada solo para determinar qué clases se nombran y qué funciones contienen esas clases. Ahora debe preocuparse por encontrar clases y funciones en las que las funciones puedan estar dentro o fuera de una clase. Esto es algo que un analizador C ++ no tiene que preocuparse ya que analiza todo en orden.

Ahora no me malinterpreten, probablemente podría hacerse en C #, y probablemente usaría esa característica. Pero, ¿realmente vale la pena la molestia de superar estos obstáculos cuando podría simplemente escribir un nombre de clase frente a un método estático?

Las funciones gratuitas son muy útiles si las combinas con la tipificación de pato. Todo el C ++ STL está basado en él. Por lo tanto, estoy seguro de que C # introducirá funciones gratuitas cuando logren agregar verdaderos generics.

Al igual que la economía, el diseño del lenguaje también se trata de psicología. Si creas apetito por generics verdaderos a través de funciones gratuitas en C # y no entregas, entonces matarías C #. Entonces, todos los desarrolladores de C # pasarían a C ++ y nadie quiere que eso suceda, ni la comunidad de C #, ni tampoco los que invirtieron en C ++.

Si lo piensas bien, el argumento contrario sería “¿por qué C ++ no admite métodos de extensión?” Y la respuesta se encuentra dentro de sus objectives de diseño.

C ++ le da funciones de espacio de nombres a las que puede llamar en cualquier objeto que lo ayude a “extender” ese objeto de alguna manera.

La mayoría de las veces que utilicé funciones de espacio de nombres en C ++ fueron para crear funciones que aceptan objetos y funciones que no quiero poner dentro de una función de miembro de clase.

En C #, puede crear un método de extensión que haga el trabajo por usted (la mayoría de las veces). Para lo que queda de los casos, perderás esa característica.

Tome el siguiente código, por ejemplo:

 template unsigned int findCount(vector& vec, T data){ unsigned int count = 0; for(auto val : vec) if(val == data) ++count; return count; } 

Ahora si necesitaba esa funcionalidad para algún propósito en mi clase, solo puedo agregarla al espacio de nombres de la clase y usarla sin “contaminar” mi clase con esa función.

En C # puedes lograr el mismo objective con el método de extensión:

 static class Extensions { public static uint FindCount(this List list, T data) { uint counts = 0; foreach (var item in list) if (item.Equals(data)) ++counts; return counts; } } 

Y ambos funcionan de la misma manera, lo cual es genial. Muchos discutirán acerca de los métodos de extensión que faltan en C ++, muchos discutirán sobre las funciones de espacio de nombres que faltan en C #.

Creo que la mejor manera de expresslo es no comparando estos lenguajes con algún detalle porque son idiomas diferentes con implementaciones diferentes.

Como citaron en su pregunta, C ++ / CLI no admite “realmente” las funciones del miembro del espacio de nombres, ya que agrega una clase sin nombre para usarlas que de ninguna manera está cerca de la implementación de C ++, tal vez en la forma en que se ve, pero realmente diferente.

Al final, la funcionalidad siempre es algo que esperar, y tanto como quiero las funciones de miembro del espacio de nombres en C # es tanto como quiero los métodos de extensión en C ++. Lo cual no es mucho de todos modos.