为什么使用“partial”时,类级别的“unsafe”修饰符不一致?

6
我注意到在部分类中使用类级别的unsafe修饰符时存在一定的行为。我希望能够得到一些澄清。
我正在开发一个相当大的包装器,为了保持理智,我使用partial修饰符将其拆分成多个文件。该包装器广泛使用unsafe指针,因此我选择在类级别上声明它以覆盖其中的所有内容。
public static unsafe partial class Ruby
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static VALUE CLASS_OF(VALUE obj) => ((RBasic*) obj)->klass;
}

在另一个文件中:
public static unsafe partial class Ruby
{
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public static void* DATA_PTR(VALUE obj) => ((RData*) obj)->data;
}

"

unsafe修饰符对于每个部分声明是必需的,以便允许并编译不安全代码,这是可以理解的,我希望部分类的声明需要完全匹配。

但按照这个逻辑,我也被允许拥有另一个文件,该文件不是unsafe

"
[SuppressUnmanagedCodeSecurity]
public static partial class Ruby
{
    [DllImport(RUBY_LIBRARY, CallingConvention = CallingConvention.Cdecl)]
    public static extern VALUE rb_ivar_get(VALUE obj, ID name);
}

在这里,我没有使用unsafe修饰符,这是完全可以的(显然此文件中没有不安全的代码)。
我希望能够澄清为什么允许这样做。难道每个部分类的类声明不应该完全匹配吗?更改/排除任何其他类修饰符是不允许的,例如privatepublicabstract等,那么为什么在使用unsafe时就没问题了呢?我认为这种行为不一致。我的猜测是编译器以某种方式在不同的上下文中运作,但这只是我自己的推测,希望比我更有知识的人能给我一些启示。

你在项目的构建属性中标记了“允许不安全代码”吗?你的部分类声明是否符合 https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods 中的指南。 - M.Hassan
当然,我允许不安全的编译,否则没有它,我甚至都没法注意到这个问题。 - ForeverZer0
1个回答

11

关于修饰符需要一致性的说法有些不准确。例如,这是完全有效的:

public abstract partial class Foo { }

partial class Foo { }

Foo表示一个抽象类,因为其中的某个部分被声明为抽象。关于部分类修饰符的规则可以在规范的第10.2.2节中找到。以下是与 unsafe 部分类有关的文本:

  

当在部分类声明上使用 unsafe 修饰符时,只有那个特定部分被视为不安全上下文(§18.1)。

当将 sealedabstract 应用于部分类时,该类的所有部分都被视为 sealedabstract。但是,它不能同时是两者。

事实上,在部分类上,唯一必须在所有部分之间保持一致但不需要在所有部分中设置的类修饰符是可访问性。

  

当部分类声明包括可访问性规范(public、protected、internal 和 private 修饰符)时,它必须与包括可访问性规范的所有其他部分一致。如果部分类没有包含可访问性规范的部分,则该类型会获得适当的默认可访问性(§3.5.1)。

这基本上意味着不允许同时声明 public partial class Barinternal partial class Bar,因为可访问性在部分之间变得不一致。未包含可访问性的其他附加部分将默认使用已声明的可访问性或规范中概述的规则所指定的默认值。

注意:我引用的规范部分来自 Visual Studio 2017 版本的规范。

下面是 ECMA-334 的相关引用:

23.2:

  

当在部分类声明上使用 unsafe 修饰符(§15.2.7)时,只有那个特定部分被视为不安全上下文。

15.2.2.1:

  

当部分类声明(§15.2.7)包括可访问性规范(通过 public、protected、internal 和 private 修饰符),该规范应与包括可访问性规范的所有其他部分相一致。如果部分类没有包含可访问性规范的部分,则该类型会获得适当的默认可访问性(§8.5.2)。

15.2.2.2 和 15.2.2.3 规定了关于 abstractsealed 修饰符的规则。


是的,我的关于abstract等的陈述是不正确的(我认为我从未真正测试过那种行为),但是abstract在所有文件中都被传递。对我个人来说,unsafe没有这样做似乎很奇怪。感谢您找到澄清这一点的方法。 :-) - ForeverZer0
@ForeverZer0 在这种情况下,似乎更好的选择是避免使用 unsafe,因为它是不安全的。 - Jonathon Chase
没错,我想我没有考虑到 C# 对类型安全的绝对热爱,这可以帮助说明行为设计背后的“原因”。再次感谢您的帮助和详细解释。 :-) - ForeverZer0
@Jonathon Chase,请在答案中引用C#规范ECMA-334版,因为这些引用在不同版本中是不同的。 - M.Hassan
2
@M.Hassan 完成。 - Jonathon Chase

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接