Можно ли вернуть ссылку на переменную в C #?

Могу я, например, вернуть ссылку на двойное значение?

Вот что я хочу сделать:

ref double GetElement()
{
   ......
   // Calculate x,y,z
   return ref doubleArray[x,y,z];
}

Чтобы использовать это так

void func()
{
   GetElement()=5.0;
}

Это похоже на возврат двойного указателя в C ++ ... Я знаю, что я написал это неправильно ... но есть ли правильный способ сделать это?


person Betamoo    schedule 27.12.2010    source источник
comment
вы хотите присвоить элемент массиву или получить его значение? Вам нужна функция, которая что-то читает или что-то пишет?   -  person Axarydax    schedule 28.12.2010
comment
Я хочу присвоить значение функции вызывающего абонента.   -  person Betamoo    schedule 28.12.2010
comment
Вы не только не можете этого сделать, но и пытаетесь вернуть ссылку на экземпляр типа значения. Я даже не знаю, что это бы значило.   -  person John Saunders    schedule 28.12.2010
comment
В этом проблема, я не знаю, как вернуть ссылку на double, а не на значение ...   -  person Betamoo    schedule 28.12.2010
comment
@John Saunders, я думаю, что это будет означать что-то в C ++ (конечно, не с таким же синтаксисом) ... но я немного заржавел в C ++, поэтому я не уверен   -  person Thomas Levesque    schedule 28.12.2010
comment
double - это тип значения. Период.   -  person John Saunders    schedule 28.12.2010
comment
Не в этом дело. В C ++ (не C ++ / CLI) отсутствует понятие типа значение / ссылка. У вас может быть ссылка на double (конечно, не ссылка в смысле .NET): int x = 42; int& y = x;. Во всяком случае, я боюсь, что это не очень поможет OP;)   -  person Thomas Levesque    schedule 28.12.2010
comment
@John Saunders: Нет, это не означает попытку вернуть ссылку на экземпляр типа значения. Это означает попытку вернуть ссылку на переменную типа значения, что совершенно разумно и законно в системе типов CLR. У переменной есть местоположение, тип и время жизни; если известно, что время жизни не короче, чем время жизни ссылки, ссылка на такую ​​переменную является законной. (В системе типов CLR, а не в C #.) Помните, элемент массива - это переменная, а не значение.   -  person Eric Lippert    schedule 28.12.2010
comment
@ Эрик: Спасибо. Я думал, что элемент массива типа значения - это просто значение, а не переменная.   -  person John Saunders    schedule 28.12.2010
comment
Если вас это интересует: я думаю (хотя мне пришлось бы проверить), что это разрешено Common Language Runtime (с управляемыми ссылками), но C # просто не поддерживает это.   -  person user541686    schedule 28.12.2010
comment
@Ламберт. Правильный. Смотрите мой ответ.   -  person Eric Lippert    schedule 28.12.2010
comment
Неужели я единственный, кто думает, что пример, предоставленный OP, - это запах кода? Мне кажется странным назначать метод, который что-то получает. Или, если на то пошло, вообще что-то назначать методу ...   -  person jasonh    schedule 28.12.2010


Ответы (5)


Обновлять

Эта функция была добавлена ​​в C # 7. Вы можете использовать синтаксис точно так же, как вы указали в своем вопросе. Например:

double[,,] doubleArray = new double[10,10,10];

ref double GetElement()
{
   var (x,y,z) = (1,2,3);
   return ref doubleArray[x, y, z];
}

Подробно подробно рассказывается в ответе Эрика Липперта. Я бы, вероятно, удалил этот ответ, но, поскольку это принятый ответ, я не могу его удалить.

Оригинальный ответ

Типы значений в C # всегда передаются по значению. Ссылка на объекты всегда передается по значению. Это изменяет небезопасный код, как указывает Axarydax.

Самый простой и безопасный способ избежать этого ограничения - убедиться, что ваш дубль каким-то образом прикреплен к объекту.

public class MyObjectWithADouble {
    public double Element {get; set;} // property is optional, but preferred.
}

...
var obj = new MyObjectWithADouble();
obj.Element = 5.0

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

Думаю, я немного лучше понимаю, на что вы сейчас собираетесь. Вы хотите вернуть местоположение значения в данном массиве, а затем иметь возможность изменить значение в этом месте. Этот шаблон нарушает некоторые из ожидаемых парадигм C #, поэтому я предлагаю рассмотреть другие способы достижения того, что вы ищете. Но если это действительно имеет смысл, я бы сделал что-то вроде этого:

public class 3dArrayLocation {public int X; public int Y; public int Z;}

...

public 3dArrayLocation GetElementLocation(...)
{
    // calculate x, y, and z
    return new 3dArrayLocation {X = x, Y = y, Z = z}
}

...

var location = GetElementLocation(...);
doubleArray[location.X, location.Y, location.Z] = 5.0;
person StriplingWarrior    schedule 27.12.2010
comment
Не волнуйтесь, массив состоит не из трех измерений, а из восьми ... Некоторому машинному обучению с Q-Learning это нужно :) - person Betamoo; 28.12.2010
comment
@Betamoo: звучит весело. Смотрите мою правку. - person StriplingWarrior; 28.12.2010

ОБНОВЛЕНИЕ: желаемая функция теперь поддерживается в C # 7.


Система типов CLR поддерживает методы возврата ссылки, и я написал экспериментальный прототип компилятора C #, который поддерживает нужную вам функцию. (Прототип также реализует локальные переменные с ссылочным типом, но поля с ссылочным типом недопустимы в системе типов CLR.)

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

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

Например, вы можете сделать пару делегатов:

struct Ref<T>
{
    private readonly Func<T> getter;
    private readonly Action<T> setter;
    public Ref(Func<T> getter, Action<T> setter)
    {
        this.getter = getter;
        this.setter = setter;
    }
    public T Value { get { return getter(); } set { setter(value); } }
}

var arr = new int[10];
var myref = new Ref<int>(()=>arr[1], x=>arr[1]=x);
myref.Value = 10;
Console.WriteLine(myref.Value);

Это значительно медленнее, чем та же функция, реализованная с возвратом ref, но преимущество состоит в том, что вы можете сделать Ref<T> в местах, где ref не разрешен. Например, вы можете сохранить Ref<T> в поле, чего нельзя сделать с помощью метода возврата ссылки.

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

См. Также связанные вопросы:

Могу ли я использовать ссылку внутри функции C #, такой как C ++?

Почему C # не поддерживает возврат ссылок? < / а>

и мое сообщение в блоге на эту тему:

http://ericlippert.com/2011/06/23/ref-returns-and-ref-locals/

person Eric Lippert    schedule 28.12.2010
comment
У меня есть сценарий, который, я сомневаюсь, можно квалифицировать как потрясающий или убедительный, но я снова сталкиваюсь с ним: класс, который представляет структуру как свойство. Если бы можно было поддерживать только Class.Struct.Property = value, можно было бы эффективно обновить это значение. Как бы то ни было, вы должны использовать Class.Struct = new Struct { Property = value } или добавить в Class избыточные вспомогательные свойства. Даже Ref<Struct> не решает эту проблему. - person Rick Sladkey; 04.01.2011
comment
@Rick: это еще одна причина, по которой изменяемые структуры - худшая практика. Если вы хотите изменить часть определенного экземпляра структуры, вы рассматриваете ее как ссылочный тип; сделайте его ссылочным типом в первую очередь. - person Eric Lippert; 04.01.2011
comment
Концептуально я согласен, но изменяемые структуры, которые выглядят как ссылочные типы, необходимы для эффективности. Если бы выделение / освобождение миллиона ссылочных экземпляров происходило так же быстро, как и миллион структур, это не было бы проблемой! - person Rick Sladkey; 04.01.2011
comment
@EricLippert: Предположим, у одного есть изменяемый класс PointClass с полем Int32 или свойствами, называемыми X и Y, а у одного есть IList<PointClass> myList. Есть ли какой-нибудь приличный способ разрешить myList[2].X = 5; работать, не позволяя внешнему коду вносить будущие изменения в myList[2].X без прохождения myList? Учитывая IList<Point> myList2, такой код, как {var tmp=myList2[2]; tmp.X = 5; myList[2] = tmp;}, яснее и безопаснее, чем все, что я могу представить для классов. Поскольку Point семантически представляет собой пару целых чисел, почему бы не использовать тип данных, который ведет себя как один? - person supercat; 15.11.2012
comment
Конечно, если бы myList[2] мог возвращать Point&, тогда код можно было бы написать самым ясным идиоматическим образом, не подвергая опасности беспорядочные ссылки на изменяемые объекты. - person supercat; 15.11.2012
comment
@EricLippert прошло некоторое время с тех пор, как эта история была опубликована, но единственная наиболее веская причина, по которой вы хотите разрешить это, - это когда вы начинаете сталкиваться с проблемами производительности сборщика мусора и начинаете нуждаться в перемещении больших объемов данных в структуры, чтобы остановить уничтожение сборщика мусора (особенно когда они мигрируют в коллекцию 2-го поколения, а затем выбрасываются). samsaffron.com/archive/2011/10/28/ - person Kendall Bennett; 20.03.2016
comment
@EricLippert, правильно ли я понимаю, что эта функция наконец-то была введена в язык (docs)? Этот ответ заслуживает обновления? - person kmote; 08.05.2018
comment
@kmote: Прочтите ответ еще раз. Я обновил его в 2017 году. Обратите внимание на бит, который ВСЕМИ ЗАГЛАВНЫМИ буквами написано ОБНОВЛЕНИЕ. Так что да, его надо обновить, и так оно и было. - person Eric Lippert; 08.05.2018
comment
@kmote: Теперь, если вы хотите потратить усилия на обновление сайта, я рекомендую вам удалить вопрос, а не обновлять ответы. Этот вопрос (1) повторяется и (2) основан на ложном предположении. Чтобы его удалить, потребуется некоторая работа, так как другие вопросы помечают этот как свой дубликат. - person Eric Lippert; 08.05.2018

Нет, в C # это невозможно. Вы можете передавать параметры только по ссылке.

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

double Element
{
    get { return doubleArray[x,y,z]; }
    set { doubleArray[x,y,z] = value; }
}

void func()
{
   Element = 5.0;
}
person Thomas Levesque    schedule 27.12.2010

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

unsafe double* GetElementP(){
...
}
person Axarydax    schedule 27.12.2010
comment
Хотя это возможно, я бы не рекомендовал это ... вероятно, есть более эффективные способы решения проблемы, но OP должен предоставить более подробную информацию о том, что он хочет сделать. - person Thomas Levesque; 28.12.2010
comment
Вам придется зафиксировать массив на месте, чтобы сборщик мусора не перемещал его. - person Eric Lippert; 28.12.2010
comment
@Eric: Я знаю, что это может быть несколько не по теме для вас, но было бы ужасно приятно, если бы кто-нибудь написал в блоге о том, как C ++ / CLI interior_ptr и pin_ptr отображаются в систему типов CLR, как сравниваются переменные указателя C # и есть ли C # эквивалент указателей C ++ / CLI. - person Ben Voigt; 28.12.2010
comment
@Ben: Стэн Липпман или Херб Саттер были бы лучше, если бы попросили детали C ++ / CLI; Я с ними не знаком. Эквивалент закрепленного указателя C ++ в C # - это локальная переменная типа указателя, объявленная с помощью оператора fixed. - person Eric Lippert; 28.12.2010

Подумав, почему бы не отправить значение, которое нужно установить, с помощью такой функции:

void SetElement(double value)
{
   ......
   // Calculate x,y,z
   doubleArray[x,y,z]=value;
}

и используйте это:

void func()
{
   SetElement(5.0);
}

Однако я все же выберу один из ваших полезных ответов ...

person Betamoo    schedule 27.12.2010