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
时有任何看不见的细微差别,它在代码中与 Utf8JsonReader
和 Memory<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
,而对于非只读结构,编译器会创建防御副本 每次 该结构用于确保该结构是只读的。这会显着降低性能。
在你的情况下,Utf8JsonReader
是 ref 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.
如果我在 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
时有任何看不见的细微差别,它在代码中与 Utf8JsonReader
和 Memory<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
,而对于非只读结构,编译器会创建防御副本 每次 该结构用于确保该结构是只读的。这会显着降低性能。
在你的情况下,Utf8JsonReader
是 ref 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.