Методы расширения, определенные для типов значений, не могут использоваться для создания делегатов - почему бы и нет?

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

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' не может использоваться для создания делегатов.

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

Ответ 1

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

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

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

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

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

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

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

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

Ответ 2

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

Оригинал

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

ИЗМЕНИТЬ

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

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, может объяснить, почему?