Есть ли способ отвлечь 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# делает вывод типов. Смотрите обсуждение в этой другой теме

Другие вопросы по тегам