Есть ли способ отвлечь myFunc (1, 2, 3) от myFunc (new int [] {1, 2, 3})? - PullRequest
9 голосов
/ 13 марта 2012

Вопрос ко всем вам, волшебники 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 []. Длина = 3, с obs [0] = 1 и т. Д.

И я ожидал, что последний будет отображаться как obs []. Length = 1, с obs [0] = {int [3]}

Можно ли это сделать, или я спрашиваю о невозможном?

Ответы [ 6 ]

9 голосов
/ 15 марта 2012

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

Прежде всего, давайте попробуем сформулировать реальный вопрос.Вот мой пример:


Преамбула:

Метод "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) вы должны просто отказаться от методов с переменным числом мест;они не подходят для вашей проблемной области.Не боритесь против инструмента;выберите другой инструмент.

9 голосов
/ 13 марта 2012

Ну, это будет сделано:

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[] нет. Если это реальная ситуация, которая доставляет вам проблемы, тогда посмотрите на динамическую версию - которая будет работать в обоих случаях.

3 голосов
/ 13 марта 2012

Да, вы можете проверить длину параметров и тип аргумента, см. Следующий пример рабочего кода:

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
        }
    }
}
2 голосов
/ 14 марта 2012

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

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;
   }
}
0 голосов
/ 15 марта 2012

Оказывается, что была настоящая проблема, и все сводится к тому, как C # делает вывод типов. Смотрите обсуждение на этой другой теме

0 голосов
/ 13 марта 2012

Это невозможно в C #.C # заменит ваш первый вызов на ваш второй вызов во время компиляции.

Одна из возможностей - создать перегрузку без params и сделать его параметром ref.Это, вероятно, не имеет смысла.Если вы хотите изменить поведение в зависимости от ввода, возможно, дайте второму myFunc другое имя.

Обновление

Теперь я понимаю вашу проблему.То, что вы хотите, не возможно.Если единственным аргументом является что-то, что может быть разрешено до object[], его невозможно отличить от этого.

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

...