Ошибка параметра общего параметра метода C #? - PullRequest
5 голосов
/ 18 мая 2010

Мне кажется, что в компиляторе C # есть ошибка / несоответствие.

Это прекрасно работает (первый метод вызывается):

public void SomeMethod(string message, object data);
public void SomeMethod(string message, params object[] data);

// ....
SomeMethod("woohoo", item);

Тем не менее это вызывает ошибку «Вызов неоднозначен между следующими методами»:

public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);

// ....
SomeMethod("woohoo", (T)item);

Я мог бы просто использовать дамп первого метода целиком, но так как это очень чувствительная к производительности библиотека и первый метод будет использоваться примерно в 75% случаев, я бы предпочел не всегда заключать объекты в массив и создавать экземпляры итератор для перехода через foreach, если есть только один элемент.

Разделение на разные именованные методы в лучшем случае было бы беспорядочным.

Мысли

EDIT:

Полагаю, Эндрю может быть чем-то заинтересованным.

Полный пример:

public static class StringStuffDoer
{
    public static string ToString<T>(T item1, T item2)
    {
        return item2.ToString() + item1.ToString();
    }

    public static string ToString<T>(T item, params T[] items)
    {
        StringBuilder builder = new StringBuilder();

        foreach (T currentItem in items)
        {
            builder.Append(currentItem.ToString());
        }

        return item.ToString() + builder.ToString();
    }

    public static void CallToString()
    {
        ToString("someString", null); // FAIL
        ToString("someString", "another string"); // SUCCESS
        ToString("someString", (string)null); // SUCCESS
    }
}

Я все еще думаю, что странно, что приведение необходимо - вызов не является двусмысленным. Это работает, если вы замените T строкой или объектом или любым неуниверсальным типом, так почему бы не сработать для универсального? Он правильно находит два возможных метода сопоставления, поэтому я считаю, что по спецификации он должен выбрать тот, который не использует params, если это возможно. Поправь меня, если я здесь не прав.

(НЕ ТАК) ФИНАЛЬНОЕ ОБНОВЛЕНИЕ:

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

РЕАЛЬНОЕ ЗАКЛЮЧИТЕЛЬНОЕ ОБНОВЛЕНИЕ:

Хорошо, вот почему проблема не обнаружилась в моем неуниверсальном тесте. Я использовал «объект» в качестве параметров типа. SomeMethod (object) и SomeMethod (params object []) НЕ выдают неоднозначную ошибку, я полагаю, что null автоматически преобразуется в «object». Я бы сказал, немного странно, но, может быть, немного понятно.

Итак, как ни странно, этот вызов работает:

SomeMethod<object>("someMessage", null);

Ответы [ 4 ]

16 голосов
/ 18 мая 2010

Мне кажется, что в компиляторе C # есть ошибка / несоответствие.

Конечно, в компиляторе есть ошибки и несоответствия. Вы не нашли ни одного из них. Компилятор ведет себя совершенно корректно и в соответствии со спецификацией во всех этих случаях.

Я делаю все возможное, чтобы разобраться в этом очень запутанном вопросе. Позвольте мне разбить его на ряд вопросов.

Почему это удается и вызывает первый метод?

public void SomeMethod(string message, object data);
public void SomeMethod(string message, params object[] data);
// ....
SomeMethod("woohoo", item);

(Предположение: этот элемент является выражением типа времени компиляции, отличным от object [].)

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

Почему это не удается из-за ошибки неоднозначности?

public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", (T)item);

Невозможно сказать, потому что вы не говорите, что такое "Т". Откуда T в этом примере? Существует два параметра типа с именем T объявлено; этот код в контексте одного из этих методов? Так как это разных типов, оба с именем T, это может иметь значение. Или это еще третий тип, называемый Т?

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

Почему это не удается из-за ошибки неоднозначности?

public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", "hello");

Это не так. Это успешно. Вывод типа выбирает "строку" для T в обоих методах. Оба общих метода применимы; вторая применима в развернутом виде, поэтому она проигрывает.

Хорошо, тогда почему это происходит с ошибкой неоднозначности?

public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", null);

Это не так. Он завершается с ошибкой «не могу определить T». Здесь недостаточно информации, чтобы определить, что такое T в любом случае. Поскольку вывод типа не может найти метод-кандидат, набор кандидатов пуст и разрешению перегрузки нечего выбирать.

Так что это удается, потому что ...?

public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod("woohoo", (string)null);

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

Что если мы возьмем вывод типа из картинки? Почему это неоднозначно?

public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod<string>("woohoo", null);

Теперь у нас есть три подходящих кандидата. Первый метод, второй метод в его обычной форме и второй метод в его расширенной форме. Расширенная форма отбрасывается, потому что расширенная форма хуже, чем обычно. Это оставляет два метода в их нормальной форме, один принимает строку, а другой - строку []. Что лучше?

Когда мы сталкиваемся с этим выбором, мы всегда выбираем тот, который имеет более конкретный тип. Если бы вы сказали

public void M(string s) { ... }
public void M(object s) { ... }
...
M(null);

мы бы выбрали строковую версию, потому что строка более конкретна, чем объект . Каждая строка является объектом, но не каждый объект является строкой.

строка не преобразуется в строку []. строка [] не может быть преобразована в строку. Ни один из них не является более конкретным, чем другой. Поэтому это ошибка неоднозначности; Есть два «лучших» кандидата.

Тогда почему это удается и что оно делает?

public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, params T[] data);
// ...
SomeMethod<object>("woohoo", null);

Опять у нас три кандидата, а не два. Мы автоматически исключаем развернутую форму, как и раньше, оставляя две. Из двух методов в нормальной форме, которые остаются, что лучше?

Мы должны определить, что является более конкретным. Каждый объектный массив является объектом, но не каждый объект является объектным массивом. object [] более специфичен, чем object, поэтому мы решили вызвать версию, которая принимает объект []. Мы передаем массив с нулевым параметром, который почти , конечно, не то, что вы хотели.

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

Лучший способ создать такую ​​логику: (Обратите внимание, что я на самом деле не скомпилировал этот код, это просто верхзав.)

static string ToString<T>(T t)
{
    return t == null ? "" : t.ToString();
}
static string ToString<T>(T t1, T t2)
{
    return ToString<T>(t1) + ToString<T>(t2);
}
static string ToString<T>(T t1, T t2, params T[] rest)
{
    string firstTwo = ToString<T>(t1, t2);
    if (rest == null) return firstTwo;
    var sb = new StringBuilder();
    sb.Append(firstTwo);
    foreach(T t in rest)
      sb.Append(ToString<T>(t));
    return sb.ToString();
}

Теперь каждый случай обрабатывается с разумной семантикой и приличной эффективностью.И для любого данного сайта вызова вы можете сразу предсказать, какой именно метод будет вызван;Есть только три варианта: один аргумент, два аргумента или более двух аргументов.Каждый обрабатывается однозначно определенным методом.

3 голосов
/ 18 мая 2010

Вы должны изменить подписи, чтобы быть более конкретными. Например:

void Foo(object o1);
void Foo(object o1, object o2);
void Foo(object o1, object o2, object o3, params object[] rest);

Обратите внимание на разницу между последними 2. Это решает проблему неоднозначности (будет работать и для дженериков).

Обновление:

public void SomeMethod<T>(string message, T data);
public void SomeMethod<T>(string message, T data1, T data2, params T[] data);

Всего 2 метода. Нет двусмысленности.

3 голосов
/ 18 мая 2010

Кажется, работает для меня, остальная часть вашего кода выглядит следующим образом?

class TestThing<T>
{
    public void SomeMethod(string message, T data)
    {
        Console.WriteLine("first");
    }
    public void SomeMethod(string message, params T[] data)
    {
        Console.WriteLine("second");
    }
}


class Program
{
    static void Main(string[] args)
    {
        var item = new object();
        var test_thing = new TestThing<object>();
        test_thing.SomeMethod("woohoo", item);
        test_thing.SomeMethod("woohoo", item, item);

        Console.ReadLine();
    }
}

Прекрасно компилируется в .NET 3.5 и выводит «first» и затем «second» при запуске. Какую версию вы используете / нацеливаете?

EDIT

Проблема из вашего кода выше в том, что компилятор не может определить тип null. В равной степени справедливо предположить, что это строка или массив строк, поэтому почему она неоднозначна, когда просто нулевая, и не неоднозначна, когда вы ее конкретно приводите (т. Е. Вы говорите компилятору, как это следует обрабатывать). *

UPDATE

"То же самое можно утверждать для SomeMethod (строка, строка) и SomeMethod (string, params string []), пока это работает "

На самом деле, нет, это не так. Вы получаете ту же неоднозначную проблему метода.

public static string TypedToString(string item1, string item2)
{
  return "";
}

public static string TypedToString(string item1, params string[] items)
{
  return "";
}

public static void CallToString()
{
  TypedToString("someString", null); // FAIL
}
0 голосов
/ 19 мая 2010

Я хочу добавить этот лакомый кусочек для всех, кто сталкивался с этим, чтобы объяснить проблему неоднозначности с SomeMethod (объект) и SomeMethod (объект params []), который меня бросал. Это было выкопано из спецификации C # для меня на форумах MSDN Фигу Фэй:

10.6.1.4 Массивы параметров

Параметр, объявленный с модификатором params, является массивом параметров.

При выполнении разрешения перегрузки метод с массивом параметров может быть применим либо в его обычной форме, либо в расширенной форме (§7.4.3.1). Расширенная форма метода доступна только в том случае, если обычная форма метода неприменима, и только если метод с такой же сигнатурой, что и расширенная форма, еще не объявлен в том же типе.

Когда типом массива параметров является object [], возникает потенциальная неоднозначность между нормальной формой метода и расширенной формой для одного параметра объекта. Причина неоднозначности заключается в том, что объект [] сам по себе неявно преобразуется в объект типа. Однако эта двусмысленность не представляет проблемы, поскольку ее можно устранить, вставив приведение при необходимости.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...