web-dev-qa-db-ja.com

C#の `this`と` ref`の同等の機能

クラス内に関数がある場合:

/* 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>オブジェクトとともにコードに大きく含まれています。

私はリファクタリングを探しています(この場合、リーダーオブジェクトで拡張メソッドを使用するとはるかにうまくいきます)。

this(外部クラスの拡張メソッド)と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()}");
}
4
morleyc

refで結構です。そしてref thisは/の別の形式と同等です

ExtensionsClass.ExpectNamedProperty(参照リーダー)

その間、inは使用しないでください。

この場合、inはパフォーマンスを低下させます。

inreadonly 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は 良い記事in修飾子とそれをいつ使うべきかを説明しています。

つまり、パラメーターのように読み取り専用ではない構造体を渡してはいけません。ほぼ常にパフォーマンスが低下します

2
weichch