Переменное число входных переменных в C#

127
13 декабря 2019, 10:20

Сегодня ходил на собеседование, и одним из вопросов был:

Как в метод передать переменное число переменных?

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

Мне, к сожалению, кроме использования списка и массивов ничего в голову не пришло. Интересно узнать возможные варианты.

Вопрос был в контексте С#.

P.S. Я написал: "реализовать перегрузку методов".

Answer 1

Ключевое слово params -

void MyMethod(params object[] inputs)
{
}

Вызов

MyMethod(1, "dva", 3.0, false);

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/params

Answer 2

Грешно будет не упомянуть об __arglist!

Рассмотрим код:

public static decimal Sum(__arglist)
{
    ArgIterator i = new ArgIterator(__arglist);
    decimal result = 0;
    while (i.GetRemainingCount() > 0)
        result += (decimal)Convert.ChangeType(TypedReference.ToObject(i.GetNextArg()), typeof(decimal));
    return result;
}
...
Console.WriteLine(Sum(__arglist(-1, 12L, 4U, 0.25, 0.25m, 30UL))); // 45.5

Здесь используется недокументированная возможность C#

Дело в том, что IL, в который и компилируется наш С#, поддерживает vararg-конвенцию (то есть можно создавать функции с динамическим числом параметров, значения которых при вызове будут взяты с вершины стека. Это Вам не params, пакующий все переданное в массив)

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

[DllImport("foo.dll", CallingConvention = CallingConvention.Cdecl]
static extern int Foo(__arglist); // Нативная функция с переменным числом парметров

Давайте взглянем на IL следующего кода:

public static decimal SumParams(params object[] Objects) => 
    Objects.Aggregate(0m, (sum, i) => sum + (decimal)Convert.ChangeType(i, typeof(decimal)));
public static decimal SumVarargs(__arglist)
{
    ArgIterator i = new ArgIterator(__arglist);
    decimal result = 0;
    while (i.GetRemainingCount() > 0)
        result += (decimal)Convert.ChangeType(TypedReference.ToObject(i.GetNextArg()), typeof(decimal));
    return result;
}
...
SumParams(-1, 12L);
SumVarargs(__arglist(-1, 12L));

Сигнатура SumParams:

.method public hidebysig static valuetype [mscorlib]System.Decimal SumParams(object[]) cil managed // object[] -> decimal

Вызов SumParams:

ldc.i4.2
newarr [mscorlib]System.Object // new object[2] a
dup
ldc.i4.0
ldc.i4.m1
box [mscorlib]System.Int32
stelem.ref // a[0] = (object)(-1)
dup
ldc.i4.1
ldc.i4.s 12
conv.i8
box [mscorlib]System.Int64 
stelem.ref // a[1] = (object)((long)12)
call valuetype [mscorlib]System.Decimal Program::SumParams(object[]) // сам вызов

Теперь же сигнатура SumVarargs:

.method public hidebysig static 
    vararg valuetype [mscorlib]System.Decimal SumVarargs() cil managed // () -> decimal

Как видите, параметров у метода не указано вовсе! Лишь ключевое слово vararg помогает нам понять, что это вариативная функция

Вызов SumVarargs:

ldc.i4.m1 // -1
ldc.i4.s 12
conv.i8 // (long)12
call vararg valuetype [mscorlib]System.Decimal Program::SumVarargs(..., int32, int64) // сам вызов

Легко заметить, что никакой упаковки в массив в данном коде нет. Объекты кладутся на стек, а метод вызывается так, будто бы его сигнатура выглядит как-то так: (int, long) -> decimal. Но на деле типы параметров указаны лишь для того, чтобы понимать что и в каком количестве забирать со стека)

Answer 3

Еще один вариант, пусть уж все в одном месте будет.

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

Например:

void ExampleMethod(int a = 0, double b = 0.0, string c = ""){}

Обращение:

//без именования параметров порядок при обращении важен
//Так можно
ExampleMethod(10);
ExampleMethod(10, 3.3);
ExampleMethod(10, 3.3, "");
//Так нельзя
ExampleMethod(3.3, "");
//При именованном обращении порядок не важен
ExampleMethod(a:10, b:3.3, c:"");
ExampleMethod(b:3.3, a:10, c:"");
ExampleMethod(c:"", a:10);
ExampleMethod(b:3.3);
READ ALSO
php soap - ошибка подключения через прокси

php soap - ошибка подключения через прокси

При подключении через свою сеть все работает хорошо, однако если подключаться через корпоративный прокси то появляется такая ошибка:

161
Вывод данных из формы в таблицы

Вывод данных из формы в таблицы

Есть база данных, из неё делал вывод в таблицы по видеоурокамГде-то сделал ощибку, но уже второй день не могу понять где

154
Как вернуть изображение по ссылке если оно находится вне каталога сайта?

Как вернуть изображение по ссылке если оно находится вне каталога сайта?

Следующий код возвращает случайное фото из каталога:

138
Необходимо создать новый столбец в базе, в который будут приходить значения из других столбцов в таблице

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

У меня есть таблица в doctrine в формате phpВыглядит она следующим образом

169