Подтвердить что ты не робот

Ref возвращает ограничения в С# 7.0

Я пытаюсь понять следующий отрывок из официального сообщения в блоге о новых функциях в С# 7.0, связанных с возвратом ref.

  • Вы можете возвращать только те ссылки, которые "безопасны для возврата": и , которые указывают на поля в объектах.

  • Местные локаторы Refresh инициализируются в определенном месте хранения и не могут быть изменены для указания на другой.

К сожалению, сообщение в блоге не дает никакого кода. Был бы очень признателен, если бы кто-то мог пролить больше света на ограничения, выделенные жирным шрифтом с практическими примерами и объяснением.

Спасибо заранее.

4b9b3361

Ответ 1

У вас есть ответы, которые разъясняют ограничение, но не аргументы за ограничение.

Обоснование ограничения заключается в том, что мы никогда не должны допускать псевдоним мертвой переменной. Если у вас обычный обыденный метод, и вы возвращаете ему ссылку, то локальный мертв к моменту использования ref.

Теперь можно указать, что локаль, возвращаемая ref, может быть вставлена ​​в поле класса замыкания. Да, это решило бы проблему. Но суть этой функции заключается в том, чтобы позволить разработчикам писать высокопроизводительные недорогие механизмы, близкие к машине, и автоматически подниматься на закрытие, а затем брать на себя нагрузку на сборное давление и т.д. - работать против этой цели.

Все может немного запутанно. Рассмотрим:

ref int X(ref int y) { return ref y; }
ref int Z( )
{
  int z = 123;
  return ref X(ref z);
}

Здесь мы возвращаем ref на локальный z в подлый подход! Это также должно быть незаконным. Но теперь рассмотрим это:

ref double X(ref int y) { return ref whatever; }
ref double Z( )
{
  int z = 123;
  return ref X(ref z);
}

Теперь мы можем знать, что возвращенный ref не является ссылкой на z. Итак, можем ли мы сказать, что это законно, если типы refs, переданные в, все разные, чем типы возвращаемых ссылок?

Как насчет этого?

struct S { public int s; }
ref int X(ref S y) { return ref y.s; }
ref int Z( )
{
  S z = default(S);
  return ref X(ref z);
}

Теперь мы снова вернули ссылку на мертвую переменную.

Когда мы впервые разработали эту функцию (в 2010 году IIRC), было несколько сложных предложений для решения этих ситуаций, но мое любимое предложение было просто "сделать все незаконным". Вы не можете вернуть ссылку, которую вы получили методом ref-return, даже если он не может быть мертв.

Я не знаю, какое правило команда С# 7 закончила.

Ответ 2

Чтобы передать что-либо по ссылке, он должен быть классифицирован как переменная. Спецификация С# (переменные §5) определяет семь категорий переменных: статические переменные, переменные экземпляра, элементы массива, значения параметров, ссылочные параметры, выходные параметры и локальные переменные.

class ClassName {
    public static int StaticField;
    public int InstanceField;
}
void Method(ref int i) { }
void Test1(int valueParameter, ref int referenceParameter, out int outParameter) {
    ClassName instance = new ClassName();
    int[] array = new int[1];
    outParameter=0;
    int localVariable = 0;
    Method(ref ClassName.StaticField);  //Static variable
    Method(ref instance.InstanceField); //Instance variable
    Method(ref array[0]);               //Array element
    Method(ref valueParameter);         //Value parameter
    Method(ref referenceParameter);     //Reference parameter
    Method(ref outParameter);           //Output parameter
    Method(ref localVariable);          //Local variable
}

Первая точка, на самом деле говорящая, что вы можете возвращать возвращаемые переменные, классифицированные как ссылочные параметры, выходные параметры, статические переменные и переменные экземпляра.

ref int Test2(int valueParameter, ref int referenceParameter, out int outParameter) {
    ClassName instance = new ClassName();
    int[] array = new int[1];
    outParameter=0;
    int localVariable = 0;
    return ref ClassName.StaticField;  //OK, "ones that point into fields in objects"
    return ref instance.InstanceField; //OK, "ones that point into fields in objects"
    return ref array[0];               //OK, array elements are also "safe to return" by reference
    return ref valueParameter;         //Error
    return ref referenceParameter;     //OK, "ones that were passed to you"
    return ref outParameter;           //OK, "ones that were passed to you"
    return ref localVariable;          //Error
}

Обратите внимание, что, например, поля типов значений, вы должны учитывать статус "безопасный для возврата" охватывающей переменной. Это не всегда разрешено, как в случае, например, полей ссылочных типов:

struct StructName {
    public int InstacneField;
}
ref int Test3() {
    StructName[] array = new StructName[1];
    StructName localVariable = new StructName();
    return ref array[0].InstacneField;      //OK, array[0] is "safe to return"
    return ref localVariable.InstacneField; //Error, localVariable is not "safe to return"
}

Результат метода возврата ref считается "безопасным для возврата", если этот метод не принимает аргументы "безопасно возвращать":

ref int ReturnFirst(ref int i, ref int ignore) => ref i;
ref int Test4() {
    int[] array = new int[1];
    int localVariable = 0;
    return ref ReturnFirst(ref array[0], ref array[0]);      //OK, array[0] is "safe to return"
    return ref ReturnFirst(ref array[0], ref localVariable); //Error, localVariable is not "safe to return"
}

Хотя мы знаем, что ReturnFirst(ref array[0], ref localVariable) вернет ссылку "безопасный возврат" (ref array[0]), компилятор не может сделать это путем анализа метода Test4 в изоляции. Таким образом, результат метода ReturnFirst в этом случае считается "безопасным для возврата".

Второй пункт говорит, что декларация локальных переменных ref должна включать инициализатор:

int localVariable = 0;
ref int refLocal1;                     //Error, no initializer
ref int refLocal2 = ref localVariable; //OK

Кроме того, локальная переменная ref не может быть переназначена для указания на другое место хранения:

int localVariable1 = 0;
int localVariable2 = 0;
ref int refLocal = ref localVariable1;
ref refLocal = ref localVariable2;     //Error
refLocal = ref localVariable2;         //Error

На самом деле нет допустимого синтаксиса для переназначения локальной локальной переменной.

Ответ 3

Вы можете найти отличную дискуссию об этой функции в GitHub - Предложение: Ref Возвращения и локали.

1. Вы можете возвращать только те ссылки, которые "безопасны для возврата": только те, которые были переданные вам, и те, которые указывают на поля в объектах.

В следующем примере показано возвращение безопасной ссылки, потому что она вызывает имя вызывающего:

public static ref TValue Choose<TValue>(ref TValue val)
{
    return ref val;
}

И наоборот, небезопасная версия этого примера будет возвращать ссылку на локальную (этот код не будет компилироваться):

public static ref TValue Choose<TValue>()
{
    TValue val = default(TValue);
    return ref val;
}

2. Местные ссылки Ref инициализируются в определенном месте хранения и не могут быть мутированы, чтобы указывать на другой.

Ограничение означает, что вам нужно инициализировать локальную ссылку всегда при объявлении. Объявление типа

ref double aReference;

не будет компилироваться. Вы также не можете назначить новую ссылку на уже существующую ссылку, например

aReference = ref anOtherValue;

Ответ 4

Другие ответы на этой странице являются полными и полезными, но я хотел добавить дополнительную точку, которая заключается в том, что параметры out, которые требуется для полной инициализации вашей функции, считаются безопасными для возврата " для ref return.

Интересно, что, объединив этот факт с еще одной новой функцией С# 7, встроенная декларация 'out' переменных, позволяет моделировать универсальную встроенную декларацию локальных переменных:

вспомогательная функция:

public static class _myglobals
{
    /// <summary> Helper function for declaring local variables inline. </summary>
    public static ref T local<T>(out T t)
    {
        t = default(T);
        return ref t;
    }
};

С помощью этого помощника вызывающий объект указывает инициализацию "встроенной локальной переменной" на , назначая ref-return помощника.

Чтобы продемонстрировать помощника, следующий пример простой двухуровневой функции сравнения, которая была бы типичной для реализации (например,) MyObj.IComparable<MyObj>.Compare. Хотя это очень просто, этот тип выражения не может обойтись, требуя одной локальной переменной - без дублирования работы. Теперь нормально, что локальный блок блокирует использование выражение-body-член, что мы хотели бы сделать здесь, но проблема проста решена с помощью указанного помощника:

public int CompareTo(MyObj x) =>
                       (local(out int d) = offs - x.offs) == 0 ? size - x.size : d;

Пошаговое руководство. Локальная переменная d является "объявленной в строке" и инициализирована результатом вычисления сравнения первого уровня на основе полей offs. Если этот результат неубедителен, мы возвращаемся к возврату сортировки второго уровня (на основе полей размера). Но, в альтернативном варианте, мы все еще можем вернуть результат первого уровня, поскольку он был сохранен в локальном d.

Обратите внимание, что вышеизложенное также можно выполнить без вспомогательной функции, используя С# 7 соответствие шаблонов:

public int CompareTo(MyObj other) => 
                       (offs - x.offs) is int d && d == 0 ? size - x.size : d;

включить в начало исходных файлов:

using System;
using /* etc... */
using System.Xml;
using Microsoft.Win32;

using static _myglobals;    //  <-- puts function 'local(...)' into global name scope

namespace MyNamespace
{
   // ...

В следующих примерах показано объявление локальной переменной inline с ее инициализацией в С# 7. Если инициализация не предусмотрена, она получает default(T), назначаемую вспомогательной функцией local<T>(out T t). Это возможно только с помощью функции ref return, поскольку методы ref return являются единственными методами, которые могут использоваться как ℓ-value.

пример 1:

var s = "abc" + (local(out int i) = 2) + "xyz";   //   <-- inline declaration of local 'i'
i++;
Console.WriteLine(s + i);   //   -->  abc2xyz3

пример 2:

if ((local(out OpenFileDialog dlg) = new OpenFileDialog       // <--- inline local 'dlg'
    {
        InitialDirectory = Environment.CurrentDirectory,
        Title = "Pick a file",
    }).ShowDialog() == true)
{
    MessageBox.Show(dlg.FileName);
}

Первый пример тривиально присваивается из целочисленного литерала. Во втором примере встроенный локальный dlg присваивается из выражения конструктора (new), а затем все выражение присваивания используется для его разрешенного значения для вызова метода экземпляра (ShowDialog) для вновь созданного экземпляра, Для точной ясности в качестве отдельного примера он заканчивается, показывая, что упомянутый экземпляр dlg действительно нужно назвать в качестве переменной, чтобы получить одно из его свойств.


[edit:] Относительно...

2. Местные ссылки Ref инициализируются в определенном месте хранения и не могут быть мутированы, чтобы указывать на другой.

... Конечно, было бы неплохо иметь переменную ref с изменяемым референтом, поскольку это поможет избежать дорогих проверок границ индексации внутри тел цикла. Конечно, это и точно, почему это не допустимо. Вы, вероятно, не можете обойти это (т.е. ref к выражению доступа к массиву с индексированием, содержащим ref, не будет работать, он будет окончательно разрешен к элементу в ссылочной позиции при инициализации), но если это поможет, обратите внимание, что вы можете взять указатель ref в указатель, а это включает ref local:

int i = 5, j = 6;

int* pi = &i;
ref int* rpi = ref pi;

Console.WriteLine(i + " " + *pi + " " + *rpi);      //   "5 5 5"

pi = &j;

Console.WriteLine(i + " " + *pi + " " + *rpi);      //   "5 6 6"

Точка этого, по общему признанию, бессмысленного примерного кода заключается в том, что, хотя мы не изменили переменную ref local rpi сама по себе (поскольку "я не могу" ), она теперь имеет другой (конечный) референт.


Более серьезно, что ref local теперь позволяет, насколько уж ужесточить IL в массивах циклов индексирования массива, это метод, который я называю типом типа значения.Это позволяет эффективно использовать IL в телах циклов, которым необходимо получить доступ к нескольким полям каждого элемента в массиве типов значений. Как правило, это был компромисс между внешней инициализацией (newobj/initobj), за которым следует единый индексный доступ против неинициализации in-situ, но с учетом избыточной множественной индексации во время выполнения.

Однако при титтинге типа значения теперь мы можем полностью исключить инструкции для каждого элемента initobj/newobj IL и также иметь только одно вычисление индексирования во время выполнения. Сначала я покажу пример, а затем опишу технику в целом ниже.

/// <summary>
/// Returns a new array of (int,T) where each element of 'src' is paired with its index.
/// </summary>
public static (int Index, T Item)[] TagWithIndex<T>(this T[] src)
{
    if (src.Length == 0)
        return new (int, T)[0];

    var dst = new (int Index, T Item)[src.Length];     // i.e, ValueTuple<int,T>[]
    ref var p = ref dst[0];      //  <--  co-opt element 0 of target for 'T' staging

    ref int i = ref p.Index;  //  <-- index field in target will also control loop
    i = src.Length;    

    while (true)
    {
        p.Item = src[--i];
        if (i == 0)
            return dst;
        dst[i] = p;
    }
}

В этом примере показано сжатое, но предельное использование метода титтинг-типа; вы можете различить его твист (отданный в комментарии) самостоятельно, если вам интересно. В дальнейшем я вместо этого обсужу технику титрования типа значения в более общих терминах.

Сначала подготовьте ref locals со ссылками непосредственно на соответствующие поля в промежуточном экземпляре типа значения. Это может быть либо в стеке, либо, как показано в примере, кооптируется из последнего обработанного элемента самого целевого массива. Возможно, имеет значение ref для всего экземпляра этапа, особенно если вы используете метод кооптации.

Каждая итерация тела цикла может затем подготовить экземпляр промежуточного этапа очень эффективно, а в качестве конечного шага, когда он готов, "штамп" он продается в массив с помощью только одной операции индексирования. Конечно, если конечный элемент массива был кооптирован как экземпляр промежуточного этапа, то вы также можете оставить цикл немного раньше.