类的 in 修饰符有啥意义

Posted

技术标签:

【中文标题】类的 in 修饰符有啥意义【英文标题】:What is the point of the in modifier for classes类的 in 修饰符有什么意义 【发布时间】:2018-08-15 09:58:02 【问题描述】:

C# 7.2 为参数引入了 in 修饰符,这对结构非常有意义,尤其是对于只读结构。

也可以用于引用类型

void Method(in StringBuilder value)  

由于引用类型默认是通过引用传递的,所以上例中的in只是一个多余的修饰符吗?

value = null在你使用in时是被禁止的,这是否意味着它只通过将原始引用传递给堆位置并阻止更改来保留引用地址的副本?

【问题讨论】:

调用@EricLippert... docs.microsoft.com/en-us/dotnet/csharp/… 有帮助吗? The in parameter designation can also be used with reference types or built in numeric values. However, the benefits in both cases are minimal, if any. 我怀疑主要好处是允许您编写同时使用 genericsin 的代码(因此允许 in 使用引用类型意味着您不需要将通用代码限制为结构)。 @mjwills 这是一个很好的猜测,我也想知道它是否真的避免复制指针(不是为了任何性能提升,只是出于好奇) 这只是对值类型参数的优化。它承诺 C# 编译器不必生成额外的代码来确保复制该值。大型结构可以生成更快的代码,但复制会失败。它通常对引用类型没有用处。禁止它也没有用,当程序员决定更改类型声明时,他们会很头疼。 【参考方案1】:

in 编译为 IL 的方式与 ref 完全相同,除了 in 参数标有 IsReadOnly 属性。

这意味着in 的行为与ref 完全相同,但编译器(而非运行时)强制您不要为in 参数赋值。

所以,正如您正确指出的那样 - inreferenece-type 参数是通过引用传递的(这意味着引用不会被复制并指向原始位置),但编译器会阻止您更改它。我真的看不出它对引用类型有多大用处,但拥有它并没有什么坏处,至少在一致性方面是这样。

【讨论】:

谢谢,我知道这只是一个带有属性的ref,但是我写问题的时候没有考虑。它具有相同的行为是有意义的。 使用in 传递引用类型参数将其作为对引用的引用传递,就像通过ref 传递它一样。它改变了值的检索方式,并且在某些情况下可能具有不同的行为。【参考方案2】:

虽然其他两个答案是正确的,in 参数在生成的 IL 中最终为 ref 参数,但应注意声称这会阻止值被复制。这仅适用于只读结构。

为了证明这一点,请考虑以下代码:

using System;

public struct S1

    public int A;

    public void ChangeA(int a) => A = a;


public static class Program

    static void Main()
    
        var s1 = new S1  A = 1 ;
        S1Foo(in s1);
        Console.WriteLine(s1.A);
    

    private static void S1Foo(in S1 s) => s.ChangeA(2);

由于我们通过引用传递s1,因此可以合理地假设S1Foo 在调用ChangeA 时会改变s1 的内容。 This doesn't happen though。原因是 s1 值被复制并通过引用传递副本,以防止通过 in 参数对结构进行此类修改。

If we decompile the resultant IL,您会看到代码最终为:

public static class Program

    private static void Main()
    
        S1 s = default(S1);
        s.A = 1;
        S1 s2 = s;
        Program.S1Foo(ref s2);
        Console.WriteLine(s2.A);
    

    private static void S1Foo([IsReadOnly] [In] ref S1 s)
    
        S1 s2 = s;
        s2.ChangeA(2);
    

但是,如果我们使用readonly struct 编写类似的代码,则不会发生复制。我说的类似,因为不可能编写相同的代码,因为字段和属性必须在只读结构中是只读的(线索就在名称中):

using System;

public readonly struct S2

    private readonly int _a;
    public int A => _a;
    public S2(int a) => _a = a;

    public void ChangeA(int a)  


public static class Program

    static void Main()
    
        var s2 = new S2(1);
        S2Foo(in s2);
        Console.WriteLine(s2.A);
    

    private static void S2Foo(in S2 s) => s.ChangeA(2);

然后no copy occurs in the resultant IL。

总之:

    in 实际上是 readonly ref, 值(或引用)通过引用传递, 编译器会阻止修改该引用的字段和属性以帮助实施其只读性, 为了进一步强制参数的只读性质,在将副本的引用传递给方法之前复制非只读结构。只读结构不会发生这种情况。

【讨论】:

在第一个示例中,我了解创建结构的副本以保护结构的任何更改,但在 S1Foo 方法中,为什么它会创建第二个副本并调用 ChangeA 方法此副本s 已经是从Main 方法发送的副本的引用。如果在传入引用 s 上调用 Change 方法会发生什么?【参考方案3】:

根据我对official documentation 的理解,这意味着传递给方法的参数不会在方法本身内部更改:

in 关键字指定您正在通过引用传递参数,并且被调用的方法不会修改传递给它的值。

当将in 关键字与值类型一起使用时,这意味着不是按值传递参数(意味着创建值的新副本),而是通过引用传递 - 因此避免了不必要的复制。

【讨论】:

【参考方案4】:

对于引用类型,我能想到的唯一有用的东西就是带有约束的泛型函数。

public interface IIntContainer

    int Value  get; 


public readonly struct LargeStruct : IIntContainer

    public readonly int val0;
    public readonly int val1;
    // ... lots of other fields
    public readonly int val20;

    public int Value => val0;


public class SmallClass : IIntContainer

    public int val0;
    public int Value => val0;


public static class Program

    static void Main()
    
        DoSomethingWithValue(new LargeStruct());
        DoSomethingWithValue(new SmallClass());
    

    public static void DoSomethingWithValue<T>(in T container) where T : IIntContainer
    
        int value = container.Value;
        // Do something with value...
    

【讨论】:

以上是关于类的 in 修饰符有啥意义的主要内容,如果未能解决你的问题,请参考以下文章

Vue 中的 .sync 修饰符有啥用

比类访问修饰符限制更少的成员访问修饰符有啥用?

访问说明符和访问修饰符有啥区别?

typescript 访问修饰符和 javascript 访问修饰符有啥区别?在使用打字稿时我应该更喜欢哪一个?

@Transient 注解和瞬态修饰符有啥区别

在 C# 中,public、private、protected 和没有访问修饰符有啥区别?