Преобразование группы методов расширения в делегат с универсальным типом

У меня есть два метода расширения в IDataReader со следующими сигнатурами:

internal static List<T> GetList<T>(this IDataReader reader, Func<string, T> del)

internal static double? GetDoubleOrNull(this IDataReader reader, string columnName)

GetDoubleOrNull не имеет перегрузок.

В другом месте я могу сделать

Func<string, double?> del = reader.GetDoubleOrNull;

var x = reader.GetList(del);

or

var x = reader.GetList<double?>(reader.GetDoubleOrNull);

или просто передайте метод экземпляра, например

public double? blah(string s)

var x = reader.GetList(blah);

но я не могу сделать

var x = reader.GetList(reader.GetDoubleOrNull);

Компилятор выдает ошибку

cannot convert from 'method group' to 'System.Func<string,double?>'

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

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


person Moss    schedule 07.03.2012    source источник
comment
Очень интересно. Явное приведение var x = reader.GetList((Func<string, double?>)reader.GetDoubleOrNull) также работает. Resharper помечает приведение как избыточное, но без него компиляция не выполняется. Джон Скит рядом?   -  person Jacek Gorgoń    schedule 07.03.2012
comment
Есть ли способ вызвать Джона Скита (или, может быть, Эрика Липперта)? Произнести их имя три раза или что-то в этом роде?   -  person Chris    schedule 07.03.2012
comment
связанные: stackoverflow. com/questions/7745852/ и stackoverflow.com/questions/2057146/   -  person AakashM    schedule 07.03.2012


Ответы (2)


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

Это в разрешении группы методов. По-видимому, компилятор С# не тратит время на то, чтобы выяснить, имеет ли используемый вами метод перегрузки или нет; просто всегда требуется явное приведение. Проверить:

Что такое группа методов в C#?
Вывод метода не работает с группой методов

Группа методов, которая возвращается из reader.GetDoubleOrNull, сужается тем, к чему вы пытаетесь привести ее: GetDoubleOrNull может ссылаться на любое количество перегруженных методов с таким именем. Вы должны явно указать его.

Интересно, что вы даже не можете назначить группу методов переменной с неявным типом по той же причине:

var x = reader.GetDoubleOrNull;

не компилируется, так как требует явного приведения.

Изменить Я почти уверен, что путаница здесь связана с методами расширения:

Проверьте следующий тестовый класс:

public static class Extensions
{
    public static List<T> GetList<T>(this IDataReader reader, Func<string, T> del)
    {
        throw new NotImplementedException();
    }

    public static double? GetDoubleOrNull(this IDataReader reader, string columnName)
    {
        throw new NotImplementedException();
    }

    public static double? blah(this string s)
    {
        throw new NotImplementedException();
    }
}

Вы можете успешно позвонить

var x = reader.GetList(Extensions.blah);

Почему это может быть? blah также является статическим методом расширения, поэтому, основываясь на ваших доказательствах, может показаться, что приведенная выше строка не должна компилироваться. Чтобы еще больше усложнить ситуацию, добавим этот метод:

public static List<T> GetList2<T>(this IDataReader reader, Func<IDataReader, string, T> del) 
{ 
    throw new NotImplementedException(); 
}

Теперь вы можете позвонить

x = reader.GetList2(Extensions.GetDoubleOrNull);

и он будет правильно скомпилирован. Что дает?

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

В вашем коде вы вызываете метод расширения с синтаксисом метода экземпляра. Однако промежуточный язык (IL), сгенерированный компилятором, переводит ваш код в вызов статического метода. Поэтому принцип инкапсуляции на самом деле не нарушается. На самом деле методы расширения не могут получить доступ к закрытым переменным того типа, который они расширяют.

Итак, когда вы звоните

var x = reader.GetDoubleOrNull("myColumnName");

то, что на самом деле компилируется и выполняется, по сути это (совершенно законный вызов, даже если это метод расширения):

var x = Extensions.GetDoubleOrNull(reader, "myColumnName");

Итак, когда вы пытаетесь использовать GetDoubleOrNull в качестве аргумента для Func<string, double?>, компилятор говорит: «Я могу превратить GetDoubleOrNull в Func<IDataReader, string, double?>, потому что у него два аргумента, но я не знаю, как превратить его в Func<string, double?>».

Несмотря на то, что вы вызываете его так, как будто это метод экземпляра IDataReader с одним аргументом, это не так: это просто статический метод с двумя аргументами, который компилятор C# обманул, заставив вас думать, что это часть IDataReader.

person eouw0o83hf    schedule 07.03.2012
comment
Отличный ответ и исследование. Я хочу +1 еще раз. ;-) - person Chris; 07.03.2012
comment
Я не Джон Скит, но я делаю то, что могу :D - person eouw0o83hf; 07.03.2012
comment
Спасибо за Ваш ответ. К сожалению, я до сих пор, кажется, не понимаю проблему ясно. Первый вопрос, на который вы сослались, мне известен. Я прочитал второй, а также те, которые были опубликованы AakashM, и мне кажется, что все сводится к тому, что разрешение перегрузки не учитывает возвращаемые типы. Я это понимаю. Но как же тогда работает передача blah? В этом случае не происходит разрешения перегрузки? Если нет, то почему? Если да, то как это удается? - person Moss; 07.03.2012
comment
Не нужно его разрешать, вы сказали, что делать с публичным двойником? бла (строка с); - person Tony Hopkinson; 08.03.2012
comment
@Moss: я обновил ответ, чтобы объяснить немного больше ясности. - person eouw0o83hf; 08.03.2012
comment
@ eouw0o83hf Теперь стало намного яснее, спасибо. Однако давайте сделаем еще один шаг вперед. Хотя я могу сделать x = reader.GetList2(Extensions.GetDoubleOrNull);, я все еще не могу сделать var x = reader.GetList2(reader.GetDoubleOrNull);. Я получаю, что аргумент типа не может быть выведен из ошибки использования. - person Moss; 08.03.2012
comment
Это странно: var x = reader.GetList<double?>(reader.GetDoubleOrNull); тоже работает. :С - person Moss; 08.03.2012
comment
Я думаю, что это ошибка в компиляторе версии 4. В версии 5 это компилируется просто отлично. (Конечно, мы этого не знали, когда задавали вопрос) - person Andrew Savinykh; 26.07.2013

GetDoubleOrNull возвращает двойной? GetList ожидает IDataReader и System.Func<string,int?>

Сообщение об ошибке немного вводит в заблуждение

общественный двойник? бла (строка с)

вар х = читатель.GetList (бла);

blah является делегатом в этом вызове. В GetList(GetDoubleOrNull) это результат get doubleOrNull.

Хороший вопрос, однако. Опубликуйте ответ, помог я или нет.

person Tony Hopkinson    schedule 07.03.2012
comment
int должно быть опечаткой, изменение его на double по-прежнему дает ошибку компилятора. - person Jacek Gorgoń; 07.03.2012
comment
Вы сказали, что в GetList(GetDoubleOrNull) это результат get doubleOrNull, но это неправильно. В этом случае GetDoubleOrNull не оценивается, он обрабатывается как группа методов. - person Chris; 07.03.2012
comment
Никогда не имел в виду, что это оценивается. Какова подпись GetDoubleOrNull по сравнению с аргументом GetList. - person Tony Hopkinson; 08.03.2012