Делегировать в качестве первого параметра в метод расширения - PullRequest
9 голосов
/ 31 мая 2011

Дамы и господа,

Я недавно попробовал этот эксперимент:

static class TryParseExtensions
{
    public delegate bool TryParseMethod<T>(string s, out T maybeValue);
    public static T? OrNull<T>(this TryParseMethod<T> tryParser, string s) where T:struct 
    {
        T result;
        return tryParser(s, out result) ? (T?)result : null;
    }
}

// compiler error "'int.TryParse(string, out int)' is a 'method', which is not valid in the given context"
var result = int.TryParse.OrNull("1");  // int.TryParse.OrNull<int>("1"); doesnt work either

// compiler error: type cannot be infered....why?
var result2 = TryParseExtensions.OrNull(int.TryParse, "2"); 

// works as expected
var result3 = TryParseExtensions.OrNull<int>(int.TryParse, "3");
      var result4 = ((TryParseExtensions.TryParseMethod<int>)int.TryParse).OrNull("4");

Мне интересно две вещи:

  • Почему компилятор можетне выводить параметр типа "int"?

  • Правильно ли я понимаю, что методы расширений не обнаруживаются в типах Delegate, так как я предполагаю, что они на самом деле не относятся к этому типу (но являются "Метод "), который только совпадает с подписью делегата?Как таковой актерский состав решает это.Разве было бы невозможно включить сценарий 1 (не этот, конечно, конкретно, но в целом)?Я думаю, с точки зрения языка / компилятора, и будет ли это на самом деле полезным, или я просто (пытаюсь) безумно злоупотреблять вещами здесь?

С нетерпением жду некоторых идей.Thnx

Ответы [ 3 ]

19 голосов
/ 31 мая 2011

У вас есть несколько вопросов здесь.(В будущем я бы рекомендовал, если у вас есть несколько вопросов, разделите их на несколько вопросов, а не на одну публикацию с несколькими вопросами; вы, вероятно, получите лучшие ответы.)

Почему можнокомпилятор не выводит параметр типа "int" в:

TryParseExtensions.OrNull(int.TryParse, "2");  

Хороший вопрос.Вместо того, чтобы ответить на этот вопрос, я отсылаю вас к моей статье 2007 года, в которой объясняется, почему это не сработало в C # 3.0:

http://blogs.msdn.com/b/ericlippert/archive/2007/11/05/c-3-0-return-type-inference-does-not-work-on-member-groups.aspx

Подводя итог: в сущности, есть чепуха ипроблема с яйцом здесь.Мы должны выполнить разрешение перегрузки для int.TryParse, чтобы определить, какая перегрузка TryParse является предполагаемой (или, если ни одна из них не работает, в чем заключается ошибка.) Разрешение перегрузки всегда пытается вывести из arguments .В этом случае, однако, это именно тот тип аргумента, который мы пытаемся вывести.

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

Как видно из комментариев к этой статье, мы получили много хороших отзывов об этом.Лучшая обратная связь была получена в основном «ну, предположим, что вывод типа уже проработал типы всех аргументов, и это возвращаемый тип , который мы пытаемся вывести;В случае, если вы могли бы сделать разрешение перегрузки ".Этот анализ верен, и изменения в этом отношении вошли в C # 4. Я говорил об этом немного больше здесь:

http://blogs.msdn.com/b/ericlippert/archive/2008/05/28/method-type-inference-changes-part-zero.aspx

Правильно ли я понимаю, что методы расширений делаютне быть обнаруженным на типах делегатов, так как я предполагаю, что они на самом деле не относятся к тому типу (но являются «Методом»), который только соответствует сигнатуре делегатов?

Ваша терминология немного не подходит, нотвоя идея верна.Мы не обнаруживаем методы расширения, когда «получатель» - это группа методов .В более общем смысле, мы не обнаруживаем методы расширения, когда получатель является чем-то, что не имеет своего собственного типа, а скорее принимает тип, основанный на его контексте: группы методов, лямбда-выражения, анонимные методы и нулевой литерал - все имеют это свойство.Было бы очень странно сказать null.Whatever() и вызвать этот метод расширения для String или, что еще более странно, (x=>x+1).Whatever() и вызвать метод расширения для Func<int, int>.

Строка спецификации.которое описывает это поведение:

Неявное преобразование идентификатора, ссылки или бокса [должно существовать] из [выражения получателя] в тип первого параметра [...].

Преобразования в группах методов не являются преобразованиями идентификаторов, ссылок или блоков;они являются преобразованиями групп методов.

Разве было бы невозможно включить сценарий 1 (не этот, конечно, конкретно, но в целом)?Я думаю, с точки зрения языка / компилятора, и будет ли это на самом деле полезным, или я просто (пытаюсь) безумно злоупотреблять здесь вещами?

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

Бывают моменты, когда это будет полезно.Например, я хотел бы быть в состоянии сделать это;Предположим, у меня есть static Func<A, R> Memoize<A, R>(this Func<A, R> f) {...}:

var fib = (n=>n<2?1:fib(n-1)+fib(n-2)).Memoize();

Вместо того, что вы должны написать сегодня, а именно:

Func<int, int> fib = null;
fib = n=>n<2?1:fib(n-1)+fib(n-2);
fib = fib.Memoize();

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

2 голосов
/ 31 мая 2011

Причина первой ошибки:
int.TryParse - это группа методов, а не экземпляр объекта любого типа.Методы расширения могут быть вызваны только для экземпляров объекта.По той же причине неверен следующий код:

var s = int.TryParse;

Это также причина, по которой тип не может быть выведен во втором примере: int.TryParse является группой методов, а не типа TryParseMethod<int>.

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

1 голос
/ 31 мая 2011

Обратите внимание, что ваш код работает, если вы впервые объявите:

TryParseExtensions.TryParseMethod<int> tryParser = int.TryParse;

, а затем используйте tryParser там, где вы использовали int.TryParse.

Проблема в том, что компилятор не знает, о какой перегрузке int.Parse вы говорите. Так что он не может полностью вывести это: вы говорите о TryParse(String, Int32) или TryParse(String, NumberStyles, IFormatProvider, Int32)? Компилятор не может угадать и не будет произвольно решать за вас (к счастью!).

Но ваш тип делегата проясняет, какая перегрузка вас интересует. Поэтому назначение tryParser не является проблемой. Вы больше не говорите о «группе методов», но о хорошо идентифицированной сигнатуре метода внутри этой группы методов, называемой int.TryParse .

...