Методы расширения, определенные для типов значений, не могут использоваться для создания делегатов. Почему нет? - PullRequest
30 голосов
/ 19 июня 2009

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

static class FunnyExtension {
    public static string Double(this string str) { return str + str; }
    public static int Double(this int num) { return num + num; }
}


Func<string> aaMaker = "a".Double;
Func<string, string> doubler = FunnyExtension.Double;

Console.WriteLine(aaMaker());       //Prints "aa"
Console.WriteLine(doubler("b"));    //Prints "bb"

Если тип, который они расширяют, является типом значения, он не будет работать:

Func<int> eightMaker = 4.Double;    //Error CS1113: Extension methods 'FunnyExtension.Double(int)' defined on value type 'int' cannot be used to create delegates
Func<int, int> intDoubler = FunnyExtension.Double;  //Works

Это дает

Ошибка CS1113: определены методы расширения FunnyExtension.Double (int). Тип значения int не может использоваться для создания делегатов.

Почему они не могут?

Ответы [ 2 ]

18 голосов
/ 19 июня 2009

В ответ на мой другой ответ Эрик Смит правильно отмечает:

"... потому что это потребовало бы неявного бокса параметра типа получателя ...". Что в любом случае происходит, если вы делаете что-то вроде этого: Func f = 5.ToString; Что совершенно законно.

Размышление об этом привело меня к новому ответу. Попробуйте это для размера:

Обычные методы «экземпляра» в структурах принимают на уровне CIL «управляемый указатель» (тип &) в качестве параметра получателя. Это необходимо для того, чтобы методы экземпляра в структурах могли назначаться полям структуры. См. Раздел II, Раздел 13.3 .

Аналогично, методы экземпляра в классах принимают "ссылку на объект" (тип O) в качестве параметра получателя (разница в том, что это указатель на управляемую кучу, и его необходимо отслеживать для GC).

Поскольку и CIL & s, и O s могут быть (и реализованы) указателями, все очень просто для реализации делегата. Независимо от того, захватывает ли делегат статический метод, метод экземпляра класса или метод экземпляра структуры, все, что ему нужно сделать, это передать указатель на его _target на первый аргумент функции.

Но сценарий, который мы обсуждаем, рушит это. Метод статического расширения, принимающий int в качестве первого аргумента, требует аргумент CIL типа int32 (см. Раздел III, раздел 1.1.1). Здесь все идет не так, как надо. Я не вижу никаких причин, по которым невозможно для реализации делегатами понять, что это происходит (например, проверяя метаданные, связанные с перехваченным MethodInfo, и испускает поток, который распаковывает _target и передает его в качестве первого аргумента, но это не нужно для делегатов классических методов экземпляра на структурах, так как они ожидайте указатель в любом случае и не появится (судя по примеру в моем предыдущем неправильном ответе), который будет реализован. Очевидно, что конкретный тип значения, о котором идет речь, будет контролировать точный характер требуемого блока.

Если я не пропущу более фундаментальное препятствие для реализации (я мог бы предположить, что это может создать проблемы для верификатора, например), кажется, что может быть разумно продлить время выполнения для поддержки этого случая, но все знаки указывают на то, что это ограничение времени выполнения, а не компилятора C # как такового.

2 голосов
/ 19 июня 2009

РЕДАКТИРОВАТЬ 2 Я больше не верю этому ответу, но я оставил его здесь, чтобы тема все еще имела смысл, и чтобы люди поняли, почему это не правильно. Смотрите мой другой ответ для другого взгляда на этот вопрос.

Оригинал

Потому что это потребовало бы неявного бокса параметра получателя типа значения (поскольку поле _target в типе System.Delegate, которое содержит параметр получателя, имеет тип System.Object), что может привести к некоторому странному поведению псевдонимов, если не ожидаю этого.

EDIT

Здесь происходит что-то еще. Я запустил этот пример программы:

class Program
{
    public static int Combine(int a, int b)
    {
        return a + b;
    }

    static void Main(string[] args)
    {
        var combineMethod = typeof(Program).GetMethod("Combine");
        var add4 = Delegate.CreateDelegate(
                              typeof(Converter<int, int>),
                              4,
                              combineMethod) as Converter<int, int>;

        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine(add4(i));
        }
        Console.ReadLine();
    }
}

и получил ArgumentException: «Ошибка привязки к целевому методу». при вызове CreateDelegate. Я не уверен, почему, и поскольку соответствующий метод - это метод internalcall, Reflector не очень помогает. Документация для CreateDelegate также не сильно помогла. Я уверен, что это как-то связано с коробкой с приемником, может быть, кто-то, кто знает источник Rotor, может объяснить, почему?

...