Эквивалентная функциональность `this` и` ref` в c# - PullRequest
4 голосов
/ 28 апреля 2020

Если у меня есть функция внутри класса:

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

, которая передается по ссылке, поскольку объектом манипулируют, отличается ли это от вспомогательной функции stati c:

/*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()}");
}

Ответы [ 2 ]

4 голосов
/ 28 апреля 2020

Вы могли бы написать много методов расширения, но действительно ли они связаны? Написание каждой вещи в методах расширения будет генерировать код спагетти.

Я бы go для ключевого слова in. Преимущества in над ref в том, что вы не можете изменить параметр, но не получите копию (как «обычный» параметр). Также вы можете передавать поля только для чтения как in параметр.

private void Expect(in Utf8JsonReader reader, in JsonTokenType t)
{
   reader.Read();
    /* snip */
}
2 голосов
/ 01 мая 2020

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);

Скомпилированный IL ExpectWithRef:

// (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

Скомпилированный IL ExpectWithIn:

// (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

У Сергея Теплякова хорошая статья объяснение модификатора in и когда его использовать.

Это означает, что вы никогда не должны передавать не-только для чтения структуру, как в параметре. Это почти всегда ухудшит производительность .

...