You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This proposal explores a more generalized file accessibility modifier as opposed to e.g. private class at the top level or only file private.
Detailed design
file accessibility domain
The modifier file is permitted on types and members within types. When determining the accessibility domain of a member M with the file modifier, we perform the following steps:
Determine the accessibility domain D for member M using the definition given in the C# standard (linked above).
Intersect D with the compilation unit in which M is declared.
Consequences of this include:
file public and file internal declared accessibility both result in identical accessibility domains. Thus, it's disallowed to use file public as the declared accessibility of a member.
file protected internal, file private protected and file protected declared accessibility all result in identical accessibility domains. Therefore, it's also disallowed to use file protected internalorfile private protected as the declared accessibility of a member.
The allowed declared accessibility combinations include:
file internal, on top-level types and members within types
file protected on nested types and members within types
file private on nested types and members within types
open question: file, which uses the default accessibility depending on the context.
Members which don't allow any accessibility modifiers also cannot use the file modifier. For example, explicit interface implementations can't use the file modifier, and neither can interface members which lack a default implementation.
The compiler needs to ensure that the runtime sees these as distinct types. We propose introducing a trailing unspeakable namespace a la the following:
// src/FileA.csnamespaceNS;// Type name in metadata is: `NS.<>1_FileA.Program`fileinternalclassProgram{}// src/generated/FileB.g.csnamespaceNS;// Type name in metadata is: `NS.<>2_FileB.g.Program`fileinternalclassProgram{}
In the above, the ordinal of the file and the file's name minus its extension are used as a "trailing" namespace between the declared namespace and the type name in source. Usually we don't include an unspeakable naming scheme in the specification, but here it seems worth codifying since it will be fairly easy to observe the full name of a user-authored file accessible type with this feature. Note that the use of <> in the namespace also makes the type unspeakable when the assembly containing the type has IVT to another assembly.
This scheme also handles nested types:
// src/FileA.csnamespaceNS;internalpartialclassC{// Type name in metadata is: `NS.C+<>1_FileA.Program`fileinternalclassProgram{}}// src/generated/FileB.g.csnamespaceNS;internalpartialclassC{// Type name in metadata is: `NS.C+<>2_FileB.g.Program`fileinternalclassProgram{}}
It might be possible to observe such file internal types through an IVT via reflection. For example, by enumerating the types in an assembly with a given accessibility. If this is something we need to defend against, then it's possible some revision of the metadata representation with input from the reflection libraries team would be needed.
Non-type member signatures
We propose introducing a synthesized modreq per-file which ensures the runtime can distinguish 'file' members across different files, without the compiler or program author needing to rename the members. This also prevents the members from being used across an IVT boundary.
It's worth noting here that modreqs can't be applied to types. Otherwise, we would probably be interested in a scheme which uses modreqs on both types and non-types for uniformity.
Alternative: Non-type member name collisions
If we don't want to synthesize modreqs, we could use an alternative design: the conventional rules around member name duplication apply, even when the members are declared across different files.
// File1.cspartialclassC{fileprivatevoidM(){}}// File2.cspartialclassC{// error CS0111: Type 'C' already defines a member called 'M' with the same parameter typesfileprivatevoidM(){}}
Non-type file internal members with InternalsVisibleTo
We would like to prevent file internal and file protected members from being used across assembly boundaries.
To accomplish this, we propose introducing a modreq on members with the file modifier:
namespaceSystem.Runtime.CompilerServices{/// <summary>Indicates the member is only accessible from the file it was originally declared in.</summary>publicsealedclassFileAccessible{}}
Interface implementation
When we considered allowing file public members, there was some concern about implicit interface implementation. For example:
By disallowing file public, we also disallow all file members from implementing interface members. Therefore, we don't expect to enter a situation where the runtime unexpectedly picks a file method for an interface implementation when some evil combination of interface lists, explicit implementations and file methods is used.
Default declared accessibility
Open question: should it be allowed to use file with the default accessibility?
// equivalent to 'file internal class C1'fileclassC1{// equivalent to 'file private void M()'filevoidM(){}}
file partial members
Open question: should it be allowed to use partial with file?
The typical purpose of partial is to allow various members to be declared and implemented in different files. However, it doesn't seem to do harm to allow it here, and it doesn't seem like there's any design space which is protected by disallowing it. We would just want to ensure we give reasonable errors when file partial is used on the same method in different files.
Although it's not part of this proposal, there has been some interest in a separate proposal for an accessibility level which includes only program text within the same namespace and assembly as the declaration. We think that such a proposal wouldn't be able to work quite the same way as this one, since it seems important to make namespace internal types visible across IVTs, for example.
// Assembly1[assembly:InternalsVisibleTo("Assembly2")]namespaceNS;namespaceinternal class C
{publicvoidM(){}}// Assembly2namespaceNS{classProgram{publicstaticvoidMain(){varc=newC();// okc.M();}}}namespaceNS2{classProgram{publicstaticvoidMain(){varc=newC();// errorc.M();}}}
In this case, most likely something like a [NamespaceAccessible] attribute with a [RequiredFeature] attribute on the namespace internal type, ensuring that any compilers that consume the type understand the associated access limitations.
The text was updated successfully, but these errors were encountered:
Issue: #5529
LDM notes:
This proposal explores a more generalized
file
accessibility modifier as opposed to e.g.private class
at the top level or onlyfile private
.Detailed design
file
accessibility domainThe modifier
file
is permitted on types and members within types. When determining the accessibility domain of a memberM
with thefile
modifier, we perform the following steps:D
for memberM
using the definition given in the C# standard (linked above).D
with the compilation unit in whichM
is declared.Consequences of this include:
file public
andfile internal
declared accessibility both result in identical accessibility domains. Thus, it's disallowed to usefile public
as the declared accessibility of a member.file protected internal
,file private protected
andfile protected
declared accessibility all result in identical accessibility domains. Therefore, it's also disallowed to usefile protected internal
orfile private protected
as the declared accessibility of a member.The allowed declared accessibility combinations include:
file internal
, on top-level types and members within typesfile protected
on nested types and members within typesfile private
on nested types and members within typesfile
, which uses the default accessibility depending on the context.Members which don't allow any accessibility modifiers also cannot use the
file
modifier. For example, explicit interface implementations can't use thefile
modifier, and neither can interface members which lack a default implementation.Metadata type names
It's important that
file
types with the same name can be declared across multiple files:The compiler needs to ensure that the runtime sees these as distinct types. We propose introducing a trailing unspeakable namespace a la the following:
In the above, the ordinal of the file and the file's name minus its extension are used as a "trailing" namespace between the declared namespace and the type name in source. Usually we don't include an unspeakable naming scheme in the specification, but here it seems worth codifying since it will be fairly easy to observe the full name of a user-authored
file
accessible type with this feature. Note that the use of<>
in the namespace also makes the type unspeakable when the assembly containing the type has IVT to another assembly.This scheme also handles nested types:
It might be possible to observe such
file internal
types through an IVT via reflection. For example, by enumerating the types in an assembly with a given accessibility. If this is something we need to defend against, then it's possible some revision of the metadata representation with input from the reflection libraries team would be needed.Non-type member signatures
We propose introducing a synthesized modreq per-file which ensures the runtime can distinguish 'file' members across different files, without the compiler or program author needing to rename the members. This also prevents the members from being used across an IVT boundary.
We emit IL like the following:
It's worth noting here that modreqs can't be applied to types. Otherwise, we would probably be interested in a scheme which uses modreqs on both types and non-types for uniformity.
Alternative: Non-type member name collisions
If we don't want to synthesize modreqs, we could use an alternative design: the conventional rules around member name duplication apply, even when the members are declared across different files.
Non-type
file internal
members withInternalsVisibleTo
We would like to prevent
file internal
andfile protected
members from being used across assembly boundaries.To accomplish this, we propose introducing a modreq on members with the
file
modifier:Interface implementation
When we considered allowing
file public
members, there was some concern about implicit interface implementation. For example:By disallowing
file public
, we also disallow allfile
members from implementing interface members. Therefore, we don't expect to enter a situation where the runtime unexpectedly picks afile
method for an interface implementation when some evil combination of interface lists, explicit implementations andfile
methods is used.Default declared accessibility
Open question: should it be allowed to use
file
with the default accessibility?file partial
membersOpen question: should it be allowed to use
partial
withfile
?The typical purpose of
partial
is to allow various members to be declared and implemented in different files. However, it doesn't seem to do harm to allow it here, and it doesn't seem like there's any design space which is protected by disallowing it. We would just want to ensure we give reasonable errors whenfile partial
is used on the same method in different files.namespace
accessibility#2497
Although it's not part of this proposal, there has been some interest in a separate proposal for an accessibility level which includes only program text within the same namespace and assembly as the declaration. We think that such a proposal wouldn't be able to work quite the same way as this one, since it seems important to make
namespace internal
types visible across IVTs, for example.In this case, most likely something like a
[NamespaceAccessible]
attribute with a[RequiredFeature]
attribute on thenamespace internal
type, ensuring that any compilers that consume the type understand the associated access limitations.The text was updated successfully, but these errors were encountered: