Есть ли способ отвлечь myFunc(1, 2, 3) от myFunc(new int[] { 1, 2, 3 })?
Вопрос ко всем вам, волшебники C#. У меня есть метод, назовите его myFunc, и он принимает списки аргументов переменной длины / типа. Сигнатура аргумента самого myFunc myFunc(params object[] args)
и я использую отражение в списках (подумайте об этом немного как printf, например).
Я хочу лечить myFunc(1, 2, 3)
в отличие от myFunc(new int[] { 1, 2, 3 })
, То есть в теле myFunc я хотел бы перечислить типы моих аргументов и хотел бы в конечном итоге использовать { int, int, int}, а не int[]. Прямо сейчас я понимаю последнее: по сути, я не могу различить два случая, и они оба входят как int[].
Я хотел, чтобы первый отображался как obs[].Length=3, с obs[0]=1 и т. Д.
И я ожидал, что последний будет отображаться как obs[].Length=1, с obs[0]={ int[3] }
Можно ли это сделать, или я спрашиваю о невозможном?
6 ответов
Ну, это будет делать это:
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("First call");
Foo(1, 2, 3);
Console.WriteLine("Second call");
Foo(new int[] { 1, 2, 3 });
}
static void Foo(params object[] values)
{
foreach (object x in values)
{
Console.WriteLine(x.GetType().Name);
}
}
}
В качестве альтернативы, если вы используете DynamicObject
Вы можете использовать динамическую типизацию для достижения аналогичного результата:
using System;
using System.Dynamic;
class Program
{
static void Main(string[] args)
{
dynamic d = new ArgumentDumper();
Console.WriteLine("First call");
d.Foo(1, 2, 3);
Console.WriteLine("Second call");
d.Bar(new int[] { 1, 2, 3 });
}
}
class ArgumentDumper : DynamicObject
{
public override bool TryInvokeMember
(InvokeMemberBinder binder,
Object[] args,
out Object result)
{
result = null;
foreach (object x in args)
{
Console.WriteLine(x.GetType().Name);
}
return true;
}
}
Вывод обеих программ:
First call
Int32
Int32
Int32
Second call
Int32[]
Теперь, учитывая вышеприведенный вывод, неясно, откуда на самом деле пришел ваш вопрос... хотя, если бы вы дали Foo("1", "2", "3")
против Foo(new string[] { "1", "2", "3" })
тогда это было бы другое дело - потому что string[]
совместим с object[]
, но int[]
нет. Если это реальная ситуация, которая доставляет вам проблемы, тогда посмотрите на динамическую версию - которая будет работать в обоих случаях.
Хорошо, давайте предположим, что мы отказались от другого вопроса, в котором вы ошибочно полагаете, что все это является ошибкой компилятора, и фактически решаете ваш реальный вопрос.
Прежде всего, давайте попробуем сформулировать реальный вопрос. Вот мой выстрел в это:
Преамбула:
"Variadic" метод - это метод, который принимает неопределенное количество параметров заранее.
Стандартный способ реализации переменных методов в C#:
void M(T1 t1, T2 t2, params P[] p)
то есть, ноль или более обязательных параметров, за которыми следует массив, помеченный как "params".
При вызове такого метода метод применим либо в обычной форме (без параметров), либо в расширенной форме (с параметрами). То есть призыв к
void M(params object[] x){}
формы
M(1, 2, 3)
генерируется как
M(new object[] { 1, 2, 3 });
потому что он применим только в развернутом виде. Но звонок
M(new object[] { 4, 5, 6 });
генерируется как
M(new object[] { 4, 5, 6 });
и не
M(new object[] { new object[] { 4, 5, 6 } });
потому что он применим в своей нормальной форме.
C# поддерживает небезопасную ковариацию массивов для массивов элементов ссылочного типа. Это string[]
может быть неявно преобразовано в object[]
даже если попытка изменить первый элемент такого массива на нестроковый вызовет ошибку времени выполнения.
Вопрос:
Я хочу сделать звонок формы:
M(new string[] { "hello" });
и иметь этот акт, как метод был применим только в развернутом виде:
M(new object[] { new string[] { "hello" }});
а не нормальная форма
M((object[])(new string[] { "hello" }));
Есть ли способ в C# реализовать методы с переменным числом аргументов, которые не становятся жертвами комбинации небезопасной ковариации массива и методов, применимых преимущественно в их нормальной форме?
Ответ
Да, есть способ, но он тебе не понравится. Вам лучше сделать метод невариантным, если вы собираетесь передавать ему отдельные массивы.
Реализация Microsoft на C# поддерживает недокументированное расширение, которое позволяет использовать методы с переменным числом в стиле C, которые не используют массивы params. Этот механизм не предназначен для общего использования и включен только для команды CLR и других авторов, создающих библиотеки взаимодействия, чтобы они могли писать код взаимодействия, который соединяет C# и языки, которые ожидают методы с переменным числом в стиле C. Я настоятельно рекомендую не пытаться сделать это самостоятельно.
Механизм для этого включает использование недокументированного __arglist
ключевое слово. Основной эскиз:
public static void M(__arglist)
{
var argumentIterator = new ArgIterator(__arglist);
object argument = TypedReference.ToObject(argumentIterator.GetNextArg());
Вы можете использовать методы итератора аргументов для обхода структуры аргументов и получения всех аргументов. И вы можете использовать супермагический типизированный эталонный объект для получения типов аргументов. Можно даже использовать эту технику для передачи ссылок на переменные в качестве аргументов, но, опять же, я не рекомендую делать это.
Что особенно ужасно в этой технике, так это то, что вызывающий абонент должен сказать:
M(__arglist(new string[] { "hello" }));
что откровенно выглядит довольно брутто в C#. Теперь вы понимаете, почему вам лучше просто полностью отказаться от вариационных методов; просто сделайте так, чтобы вызывающий передавал массив и покончил с этим.
Опять же, мой совет: (1) ни при каких обстоятельствах не пытайтесь использовать эти недокументированные расширения языка C#, которые предназначены для удобства команды разработчиков CLR и авторов взаимодействующих библиотек, и (2) вы должны просто отказаться от методов с переменными числами; они не подходят для вашей проблемной области. Не боритесь против инструмента; выберите другой инструмент.
Да, вы можете, проверяя длину параметров и тип аргумента, увидеть следующий пример рабочего кода:
class Program
{
static void Main(string[] args)
{
myFunc(1, 2, 3);
myFunc(new int[] { 1, 2, 3 });
}
static void myFunc(params object[] args)
{
if (args.Length == 1 && (args[0] is int[]))
{
// called using myFunc(new int[] { 1, 2, 3 });
}
else
{
//called using myFunc(1, 2, 3), or other
}
}
}
Вы можете добиться чего-то подобного, вычеркнув первый элемент из списка и предоставив дополнительную перегрузку, например:
class Foo
{
public int Sum()
{
// the trivial case
return 0;
}
public int Sum(int i)
{
// the singleton case
return i;
}
public int Sum(int i, params int[] others)
{
// e.g. Sum(1, 2, 3, 4)
return i + Sum(others);
}
public int Sum(int[] items)
{
// e.g. Sum(new int[] { 1, 2, 3, 4 });
int i = 0;
foreach(int q in items)
i += q;
return i;
}
}
Это невозможно в C#. C# заменит ваш первый вызов на ваш второй вызов во время компиляции.
Одной из возможностей является создание перегрузки без params
и сделать это ref
параметр. Это, вероятно, не имеет смысла. Если вы хотите изменить поведение в зависимости от ввода, возможно, дайте второму myFunc другое имя.
Обновить
Теперь я понимаю вашу проблему. То, что вы хотите, не возможно. Если единственным аргументом является то, что может разрешить object[]
это невозможно отличить от этого.
Вам нужно альтернативное решение, может быть, есть словарь или массив, созданный вызывающей стороной для построения параметров.
Оказывается, что была настоящая проблема, и все сводится к тому, как C# делает вывод типов. Смотрите обсуждение в этой другой теме