Почему я не могу передать различное количество ссылок на функцию? - PullRequest
10 голосов
/ 07 декабря 2011

Я хочу сделать что-то вроде этого:

double a, b, c, d, e;
ParseAndWrite("{1, 2, 3}", ref a, ref b, ref c);
ParseAndWrite("{4, 5}", ref d, ref e);

-> a = 1, b = 2, c = 3, d = 4, e = 5

Однако я не могу написать такую ​​функцию:

private void ParseAndWrite(string leInput, params ref double[] targets)
{
   (...)
}

Это не работает, по какой-то причине нельзя использовать ref и params одновременно. Почему так?

edit: ОК, вот еще немного информации о том, зачем мне это нужно: через интерфейс я получаю множество строк, содержащих значения, с синтаксисом, подобным этому:

inputConfig : " step, stepHeight, rStep, rStepHeight, (nIterations, split, smooth) "
outputConfig : " dataSelection, (corrected, units, outlierCount, restoreOriginalRange) "

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

Ответы [ 4 ]

16 голосов
/ 07 декабря 2011

почему-то нельзя использовать ref и params одновременно. Почему так?

Рассмотрим эти три желательные характеристики камер:

  1. легкий

  2. высококачественный объектив

  3. недороги

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

Возвращаясь теперь к вашему вопросу. Рассмотрим эти три желательные характеристики среды выполнения:

  1. допустимы массивы параметров типов ref-to-variable

  2. система типа безопасна

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

Вы можете иметь любые два, но вы не можете иметь все три. Какие два вы бы хотели?

Вы можете иметь массивы ref param и систему безопасных типов за счет того, что локальные переменные ref'd распределяются в куче мусора. Насколько мне известно, никто не делает этого, но это, безусловно, возможно.

Вы можете иметь массивы ref param, все локальные переменные, выделенные во временном пуле, и систему типов, которая вылетает при неправильном использовании. Это подход "C / C ++"; можно взять ссылку и сохранить ее в месте, которое имеет более длительный срок службы, чем срок действия объекта, на который делается ссылка. Если вы сделаете это, вы можете ожидать, что ваша программа на C ++ потерпит крах и погибнет самым ужасным из возможных способов, совершив простые ошибки, которые трудно обнаружить. Добро пожаловать в пропасть отчаяния.

Или мы можем сделать массивы ref param недопустимыми, распределить все локальные переменные во временном пуле и создать систему типов, надежно защищенную от памяти. Это выбор, который мы сделали при сборке C # и CLR; добро пожаловать в яму качества.


Теперь, то, что я сказал выше, на самом деле - большая ложь. Это ложь, потому что среда выполнения и язык C # на самом деле do поддерживают функцию, похожую (но не идентичную) на массивы параметров refs. Это приятная ложь и поверь мне, ты хочешь жить в мире, где ты веришь лжи , поэтому я рекомендую тебе принять синюю таблетку и продолжать делать это.

Если вы хотите взять красную таблетку и выяснить, насколько глубоко заходит кроличья нора, вы можете использовать недокументированную функцию __arglist в C # для создания метода вариации C-стиля , а затем сделать TypedReference объекты, которые ссылаются на ссылки произвольных полей или локальных объектов структурных типов, а затем произвольно передают многие из них в метод variadic. Используйте фабричные методы TypedReference или недокументированную функцию __makeref в C #.

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

Правильное использование TypedReference объектов не для слабонервных, и снова я настоятельно рекомендую не делать этого. Я никогда не делал этого в производственном коде за много лет написания C #. Если вы хотите передать ссылки на сколь угодно много переменных, передайте массив . Массив по определению является набором переменных. Это гораздо безопаснее, чем передавать кучу объектов, представляющих управляемые адреса экземпляра или локальные переменные.

3 голосов
/ 07 декабря 2011

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

В наборе параметров variadic ref есть что-то очень странное, и я не могу себе представить, что это было бы полезно, за исключением некоторых ограниченных обстоятельств, и, как правило, существуют лучшие абстракции для возврата списка элементов.

Вы можете передать предварительно заданный размер double[] для метода заполнения, конечно, хотя я предпочитаю просто вернуть IEnumerable<double>

2 голосов
/ 07 декабря 2011

Я не знаю, является ли это единственной причиной, но вот одно из возможных объяснений:

Фактический параметр по-прежнему в конечном итоге равен double[], поэтому для того, чтобы сделать то, что вы хотите, компилятору на самом деле нужно записать это как:

{
    double[] arr = new double[] {a, b, c, d}; // copy in
    YourMethod(arr);
    a = arr[0]; // copy back out
    b = arr[1];
    c = arr[2];
    d = arr[3];
}

Тем не менее! это меняет семантику. Представьте, что вызывающий метод был на самом деле:

void SomeCallingMethod(ref double a)
{
    double b = ..., c = ... d = ...;
    YourMethod(ref a, ref b, ref c, ref d);
    /*  which, say, is translated to
    {
        double[] arr = new double[] {a, b, c, d}; // copy in
        YourMethod(arr);
        a = arr[0]; // copy back out
        b = arr[1];
        c = arr[2];
        d = arr[3];
    }  */      
}

Ожидаемое поведение заключается в том, что мы постоянно обновляем a напрямую, но в действительности мы не . Это может привести к странным и запутанным ошибкам. Действительно, чтобы сделать это правильно, «копирование назад» должно было бы перейти в finally, так как на самом деле ref значения должны показывать изменения до исключения, но это все равно не меняет семантику обновления после вызов метода , а не во время.

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


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

  • это сделает код по существу unsafe
  • если массив был сохранен где-либо (какими могут быть массивы), вызывающий метод мог бы вернуть и удалить область действия используемого локального элемента, что означает, что была бы корректно выглядящая ссылка, которая фактически указывает на мусор - серьезное повреждение Ahoy

В общем, ничего хорошего из этого не получается, и много чего плохого.

0 голосов
/ 07 декабря 2011

Причина, вероятно, техническая. «params» - это просто синтаксический сахар для аргумента массива. Таким образом, ParseAndWrite(string leInput, params double[] targets) компилируется как ParseAndWrite(string leInput, params double[] targets).

Тогда ParseAndWrite(string leInput, params ref double[] targets) будет скомпилировано как ParseAndWrite(string leInput, ref double[] targets). По сути, это будет массив, который передается как ссылка, а не фактическое содержимое массива. Который, очевидно, не будет таким поведением, ожидаемым разработчиками, и поэтому будет подвержен ошибкам.

...