Compartilhar via


Tipos de locais de arquivos

Observação

Este artigo é uma especificação de recurso. A especificação serve como o documento de design para o recurso. Ela inclui alterações de especificação propostas, juntamente com as informações necessárias durante o design e o desenvolvimento do recurso. Esses artigos são publicados até que as alterações de especificação propostas sejam finalizadas e incorporadas na especificação ECMA atual.

Pode haver algumas divergências entre a especificação do recurso e a implementação concluída. Essas diferenças são capturadas nas notas pertinentes da reunião de design de idioma (LDM).

Você pode saber mais sobre o processo de adoção de speclets de recursos no padrão de linguagem C# no artigo sobre as especificações de .

Problema do especialista: https://github.com/dotnet/csharplang/issues/5529

Resumo

Permitir o uso de um modificador file em declarações de tipo de nível superior. O tipo só existe no arquivo em que ele é declarado.

// 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.

Motivação

Nossa principal motivação vem dos geradores de código-fonte. Os geradores de origem funcionam adicionando arquivos à compilação do usuário.

  1. Esses arquivos precisam ser capazes de conter detalhes de implementação que são ocultos do restante da compilação, mas que podem ser usados em todo o arquivo em que são declarados.
  2. Queremos reduzir a necessidade de os geradores "pesquisarem" nomes de tipo que não colidem com declarações no código do usuário ou código de outros geradores.

Projeto detalhado

  • Adicionamos o modificador file aos seguintes conjuntos de modificadores:
  • O modificador de file só pode ser usado em um tipo de nível superior.

Quando um tipo tem o modificador file, ele é considerado um tipo de local de arquivo.

Acessibilidade

O modificador file não é classificado como um modificador de acessibilidade. Nenhum modificador de acessibilidade pode ser usado em combinação com file em um tipo. file é tratado como um conceito independente da acessibilidade. Como os tipos locais de arquivo não podem ser aninhados, somente a acessibilidade padrão internal pode ser usada com tipos file.

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

Nomenclatura

A implementação garante que os tipos locais de arquivo em arquivos diferentes com o mesmo nome sejam distintos para o runtime. A acessibilidade e o nome do tipo nos metadados são definidos pela implementação. A intenção é permitir que o compilador adote quaisquer recursos futuros de limitação de acesso no runtime adequados para o recurso. Espera-se que, na implementação inicial, a acessibilidade internal seja utilizada, e um nome impronunciável gerado seja usado, dependendo do arquivo em que o tipo é declarado.

Pesquisa

Alteramos a seção de pesquisa de membro da seguinte maneira (novo texto em negrito):

  • Em seguida, se K for zero, todos os tipos aninhados cujas declarações incluírem parâmetros de tipo serão removidos. Se K não for zero, todos os membros com um número diferente de parâmetros de tipo serão removidos. Quando K é zero, os métodos que têm parâmetros de tipo não são removidos, uma vez que o processo de inferência de tipo (§11.6.3) pode ser capaz de inferir os argumentos de tipo.
  • Em seguida, permita que F seja a unidade de compilação que contém a expressão em que a pesquisa de membro está ocorrendo. Todos os membros que são tipos locais de arquivo e não são declarados no F são removidos do conjunto.
  • Em seguida, se o conjunto de membros acessíveis contiver tipos locais de arquivo, todos os membros que não são tipos locais de arquivo serão removidos do conjunto.

Comentários

Essas regras não permitem o uso de tipos locais de arquivo fora do arquivo no qual são declaradas.

Essas regras também permitem que um tipo restrito a arquivo tenha um efeito de sombra em um namespace ou um tipo não restrito a arquivo.

// 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
    }
}

Observe que não atualizamos a seção de escopos da especificação. Isso ocorre porque, como diz a especificação:

O escopo de um nome é a região do texto do programa na qual é possível fazer referência à entidade declarada pelo nome sem a qualificação do nome.

Na verdade, o escopo afeta apenas a pesquisa de nomes não qualificados. Este não é exatamente o conceito certo para aproveitarmos porque precisamos também afetar a pesquisa de nomes qualificados:

// 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
    }
}

Portanto, não especificamos a característica em termos de qual escopo o tipo está contido, mas sim como "regras de filtragem" adicionais na pesquisa de membros.

Atributos

Classes locais de arquivo têm permissão para serem tipos de atributo e podem ser usadas como atributos dentro de tipos locais de arquivo e tipos não locais de arquivo, como se o tipo de atributo fosse um tipo não local de arquivo. O nome de metadados do tipo de atributo local de arquivo ainda passa pela mesma estratégia de geração de nome que outros tipos locais de arquivo. Isso significa que, provavelmente, a detecção da presença de um tipo local de arquivo por um nome de cadeia de caracteres codificado seja impraticável, pois isso requer a dependência da estratégia interna de geração de nomes do compilador, o que pode mudar ao longo do tempo. No entanto, a detecção por meio de typeof(MyFileLocalAttribute) funciona.

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 em assinaturas

Há uma necessidade geral de impedir que tipos locais do arquivo apareçam em parâmetros de membros, retornos e restrições de parâmetros de tipo, onde o tipo local do arquivo pode não estar no escopo no ponto em que o membro é utilizado.

Observe que os tipos não locais de arquivo têm permissão para implementar interfaces locais de arquivo, semelhantes à forma como os tipos podem implementar interfaces menos acessíveis. Dependendo dos tipos presentes nos membros da interface, isso pode resultar em uma violação das regras na seção a seguir.

Permita apenas o uso de assinatura em membros de tipos de arquivos locais

Talvez a maneira mais simples de garantir isso seja impor que os tipos locais de arquivo só possam aparecer em assinaturas ou como tipos base de outros tipos locais de arquivo:

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
}

Observe que isso restringe o uso em implementações explícitas, mesmo que esses usos sejam seguros. Fazemos isso para simplificar as regras para a iteração inicial do recurso.

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

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

global using static

É um erro de tempo de compilação usar um tipo local de arquivo em uma diretiva global using static, ou seja,

global using static C; // error

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

Implementação/substituições

Declarações de tipo local de arquivo podem implementar interfaces, substituir métodos virtuais etc., assim como declarações de tipo regular.

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