c# 中 `this` 和 `ref` 的等效功能

Equivalent functionality of `this` and `ref` in c#

如果我在 class 中有一个函数:

/* class snipped */
private void Expect(ref Utf8JsonReader reader, JsonTokenType t)
{
    reader.Read();
    /* snip */
}

由于正在操作对象,因此通过引用传递,这与静态辅助函数有什么不同吗:

/*static class snipped*/
static public void Expect(this Utf8JsonReader reader, JsonTokenType t)
{
    reader.Read();
    /* snip */
}

// call helper via reader.Expect(requiredToken)

我问如果使用 ref 时有任何看不见的细微差别,它在代码中与 Utf8JsonReaderMemory<T> 对象一起在嵌套函数之间传递。

我正在寻求重构(在这种情况下,在 reader 对象上使用扩展方法会更好)。

this(外部扩展方法class)和ref(函数之间通过引用传递)在功能上是否等效?

更新 - ref this 需要??

作为更新,简单地使用 this 不起作用,在 ExpectNamedProperty 函数中它会调用 reader.Expect 但在返回对象时会恢复。不知何故正在堆栈上制作副本或正在发生某些事情。

我什至不知道这是一个有效的组合,ref this 确实有效,而 this 只是没有修改。需要澄清没有做一些可怕的事情!

public static void Expect(ref this Utf8JsonReader reader, JsonTokenType t)
{
    reader.Read(); // this mutation is never passed back to caller      
}

public static void ExpectNamedProperty(ref this Utf8JsonReader reader, string expectedPropertyName)
{
    reader.Expect(JsonTokenType.PropertyName, expectedPropertyName);

    // at this point it is as if the function above was never called

    var foundPropertyName = reader.GetString();

    if (foundPropertyName != StreamFieldNames.Event)
        throw new JsonException($"expected {StreamFieldNames.Event} found {foundPropertyName} at position {reader.Position.GetInteger()}");
}

你可以写很多扩展方法,但它们真的相关吗?在扩展方法中编写所有内容都会生成意大利面条代码。

我会选择 in 关键字。 in 相对于 ref 的进步是您无法修改参数,但您不会获得副本(如 "normal" 参数)。您也可以将只读字段作为 in 参数传递。

private void Expect(in Utf8JsonReader reader, in JsonTokenType t)
{
   reader.Read();
    /* snip */
}

ref 没问题。而ref this相当于/另一种形式的

ExtensionsClass.ExpectNamedProperty(ref reader)

同时,请勿使用in

in 在这种情况下会使性能变差。

in 完美适用于 readonly struct,而对于非只读结构,编译器会创建防御副本 每次 该结构用于确保该结构是只读的。这会显着降低性能。

在你的情况下,Utf8JsonReaderref struct,而不是 readonly struct

考虑这个例子:

private void ExpectWithIn(in Utf8JsonReader reader)
{
    reader.Read();
}

private void ExpectWithRef(ref Utf8JsonReader reader)
{
    reader.Read();
}

ExpectWithRef(ref reader);
ExpectWithIn(reader);

ExpectWithRef的编译IL:

// (no C# code)
IL_0000: nop
// reader.Read();
IL_0001: ldarg.1
IL_0002: call instance bool [System.Text.Json]System.Text.Json.Utf8JsonReader::Read()
IL_0007: pop
// (no C# code)
IL_0008: ret

ExpectWithIn的编译IL:

// (no C# code)
IL_0000: nop

// The compiler creates defensive copy to make sure reader variable is readonly
// The compiler repeats this for every use of reader variable
// so this is significant impact 

// Utf8JsonReader utf8JsonReader = reader;
IL_0001: ldarg.1
IL_0002: ldobj [System.Text.Json]System.Text.Json.Utf8JsonReader
IL_0007: stloc.0


// utf8JsonReader.Read();
IL_0008: ldloca.s 0
IL_000a: call instance bool [System.Text.Json]System.Text.Json.Utf8JsonReader::Read()
IL_000f: pop
// (no C# code)
IL_0010: ret

Sergey Tepliakov a good article 解释了 in 修饰符以及何时使用它。

It means that you should never pass a non-readonly struct as in parameter. It almost always will make the performance worse.