类 的 in 修饰符有什么意义

What is the point of the in modifier for classes

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

引用类型也可以使用

void Method(in StringBuilder value) { }

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

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

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

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

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

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

The in keyword specifies that you are passing the parameter by reference and the called method does not modify the value passed to it.

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

虽然其他两个答案是正确的,即 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.

总而言之:

  1. in 实际上是 readonly ref,
  2. 值(或引用)通过引用传递,
  3. 编译器阻止修改该引用的字段和属性以帮助强制执行其 readonly-ness、
  4. 为了进一步加强参数的只读性质,然后 non-readonly 在将对副本的引用传递给方法之前复制结构。对于只读结构不会发生这种情况。

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

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