Compartir vía


Tipos locales de archivo

Nota:

Este artículo es una especificación de características. La especificación actúa como documento de diseño de la característica. Incluye cambios de especificación propuestos, junto con la información necesaria durante el diseño y el desarrollo de la característica. Estos artículos se publican hasta que se finalizan los cambios de especificación propuestos y se incorporan en la especificación ECMA actual.

Puede haber algunas discrepancias entre la especificación de características y la implementación completada. Esas diferencias se recogen en las notas de la reunión de diseño de lenguaje (LDM) correspondientes.

Puede obtener más información sobre el proceso de adopción de especificaciones de características en el estándar del lenguaje C#, en el artículo sobre especificaciones.

Problema planteado por experto: https://github.com/dotnet/csharplang/issues/5529

Resumen

Permita un modificador file en declaraciones de tipo de nivel superior. El tipo solo existe en el archivo donde se declara.

// File1.cs
namespace NS;

file class Widget
{
}

// File2.cs
namespace NS;

file class Widget // different symbol than the Widget in File1
{
}

// File3.cs
using NS;

var widget = new Widget(); // error: The type or namespace name 'Widget' could not be found.

Motivación

Nuestra motivación principal proviene de los generadores de código fuente. Los generadores de código fuente funcionan agregando archivos a la compilación del usuario.

  1. Esos archivos deben ser capaces de incluir los detalles de la implementación que están ocultos del resto de la compilación, pero se pueden usar en todo el archivo en el que se declaran.
  2. Queremos que los generadores no tengan que "buscar" siempre los nombres de los tipos que no entren en conflicto con declaraciones en el código de usuario o el código de otros generadores.

Diseño detallado

  • Hemos añadido el modificador file a los siguientes grupos de modificadores:
  • El modificador file solo se puede usar en un tipo de nivel superior.

Cuando un tipo tiene el modificador file, se dice que es un tipo local de archivo.

Accesibilidad

El modificador file no se clasifica como un modificador de accesibilidad. No se pueden usar modificadores de accesibilidad en combinación con file en un tipo. file se considera un concepto independiente de accesibilidad. Dado que los tipos locales de archivo no se pueden anidar, solo el internal de accesibilidad predeterminada se puede usar con tipos file.

public file class C1 { } // error
internal file class C2 { } // error
file class C3 { } // ok

Nomenclatura

La implementación garantiza que, para el tiempo de ejecución, los tipos locales de archivo en diferentes archivos con el mismo nombre serán distintos. La accesibilidad y el nombre del tipo en los metadatos se definen en la implementación. Lo que se pretende es permitir que el compilador adopte las funcionalidades futuras de limitación de acceso en el tiempo de ejecución que sean adecuadas para esta función. Se espera que, en la implementación inicial, se use una accesibilidad internal y se emplee un nombre generado que no se puede describir, lo cual depende del archivo en el que se declare el tipo.

Búsqueda

Modificamos la sección de búsqueda de miembros de la siguiente manera (nuevo texto en negrita):

  • A continuación, si K es cero, se eliminan todos los tipos anidados cuyas declaraciones incluyan parámetros de tipo. Si K no es cero, se eliminan todos los miembros con un número diferente de parámetros de tipo. Cuando K es cero, los métodos que tienen parámetros de tipo no se eliminan, ya que el proceso de inferencia de tipos (sección 11.6.3) podría ser capaz de inferir los argumentos de tipo.
  • A continuación, sea F la unidad de compilación que contiene la expresión en la que se realiza la búsqueda de miembros. Todos los miembros que sean tipos locales del archivo y no se declaren en F se quitan del grupo.
  • Después, si el grupo de miembros accesibles incluye tipos locales de archivo, todos los miembros que no sean tipos locales de archivo se quitarán del grupo.

Observaciones

Estas reglas no permiten usar tipos locales de archivo fuera del archivo en el que se declaran.

Estas reglas también permiten que un tipo de archivo local sobrescriba un espacio de nombres o un tipo que no sea de archivo:

// File1.cs
class C
{
    public static void M() { }
}
// File2.cs
file class C
{
    public static void M() { }
}

class Program
{
    static void Main()
    {
        C.M(); // refers to the 'C' in File2.cs
    }
}

Tenga en cuenta que no actualizamos la sección de los ámbitos de la especificación. Esto se debe a que, como indica la especificación:

El ámbito de un nombre es la región del texto del programa en el que es posible hacer referencia a la entidad declarada a través del nombre sin cualificación del nombre.

En efecto, el ámbito solo afecta a la búsqueda de nombres no calificados. Este concepto no es del todo el que debemos hacer uso, ya que también hay que hacer que se aplique en la búsqueda de nombres calificados.

// File1.cs
namespace NS1
{
    file class C
    {
        public static void M() { }
    }
}

namespace NS2
{
    class Program
    {
        public static void M()
        {
            C.M(); // error: C is not in scope
            NS1.C.M(); // ok: C can be accessed through NS1.
        }
    }
}
// File2.cs
namespace NS1
{
    class Program
    {
        C.M(); // error
        NS1.C.M(); // error
    }
}

Por ello, no especificamos la funcionalidad en relación con el ámbito en el que se encuentre el tipo, sino como reglas de filtrado adicionales en la búsqueda de miembros.

Atributos

Las clases locales de archivo pueden ser tipos de atributo y se pueden usar como atributos dentro de tipos locales de archivo y tipos no locales de archivo, como si el tipo de atributo fuera de un tipo local de archivo. El nombre de los metadatos del tipo de atributo local de archivo sigue pasando por el mismo proceso de generación de nombres que otros tipos locales de archivo. Esto significa que es probable que la detección de la presencia de un tipo local de archivo mediante un nombre de cadena codificado de forma rígida sea poco práctico, ya que se necesita que dependa del proceso de generación interna de nombres del compilador, que podría cambiar con el tiempo. Sin embargo, sí funciona la detección a través de typeof(MyFileLocalAttribute).

using System;
using System.Linq;

file class MyFileLocalAttribute : Attribute { }

[MyFileLocalAttribute]
public class C
{
    public static void Main()
    {
        var attribute = typeof(C).CustomAttributes.Where(attr => attr.AttributeType == typeof(MyFileLocalAttribute)).First();
        Console.Write(attribute); // outputs the generated name of the file-local attribute type
    }
}

Uso en firmas

Lo más normal es que se necesite evitar que los tipos locales de archivo aparezcan en parámetros de miembro, devoluciones y restricciones de parámetros de tipo donde el tipo local de archivo podría no estar en el ámbito en el momento de uso del miembro.

Tenga en cuenta que los tipos no locales de archivo pueden implementar interfaces locales de archivo, de forma similar a la manera en que los tipos pueden implementar interfaces menos accesibles. Según los tipos presentes en los miembros de la interfaz, esto podría hacer que se vulneren las reglas de la sección siguiente.

Permitir solo el uso de firmas en miembros de tipos locales de archivo

Quizás la manera más sencilla de garantizar esto es exigir que los tipos locales de archivo solo puedan aparecer en firmas o como tipos base de otros tipos locales de archivo:

file class FileBase
{
}

public class Derived : FileBase // error
{
    private FileBase M2() => new FileBase() // error
}

file class FileDerived : FileBase // ok
{
    private FileBase M2() => new FileBase(); // ok
}

Tenga en cuenta que esto restringe el uso en implementaciones explícitas, aunque dichos usos sean seguros. Lo hacemos para simplificar las reglas de la iteración inicial de la funcionalidad.

file interface I
{
    void M(I i);
}

class C : I
{
    void I.M(I i) { } // error
}

global using static

Se trata de un error en tiempo de compilación para usar un tipo local de archivo en una directiva global using static, por ejemplo.

global using static C; // error

file class C
{
    public static void M() { }
}

Implementación/anulaciones

Las declaraciones de tipos locales de archivo pueden implementar interfaces, invalidar métodos virtuales, etc. al igual que las declaraciones de tipos normales.

file struct Widget : IEquatable<Widget>
{
    public bool Equals(Widget other) => true;
}