Почему универсальные структуры не могут иметь статические члены, которые определяют универсальный тип в C#?

Извините, если это дубликат! Я искал вокруг, но не смог найти объяснение. Следующий игрушечный пример выдает исключение TypeLoadException, как только я пытаюсь создать экземпляр этой структуры. Он отлично работает, если я использую класс или вместо этого не указываю универсальный тип в статическом члене (оставьте его как T.)

public struct Point<T>
{
    static Point<int> IntOrigin = new Point<int>(0, 0);

    T X { get; }
    T Y { get; }

    public Point(T x, T y)
    {
        this.X = x;
        this.Y = y;
    }
}

Моя довольно сложная, реальная ситуация сводится к чему-то вроде этого, поэтому мне очень хотелось бы понять, почему он генерирует исключение TypeLoadException.


person Travis    schedule 12.09.2020    source источник
comment
Странный. Я могу воспроизвести вашу ошибку с помощью Mono и .NET Core. Однако с параметром .NET Framework на dotnetfiddle.net выдается ошибка компилятора CS0523.   -  person Sweeper    schedule 12.09.2020
comment
Следующий пример дает мне исключение TypeLoadException... Это прекрасно работает, если я... не указываю универсальный тип в статическом члене - я не понял; приведенный игрушечный пример не использует T в статическом члене - так он дает исключение или нет? Можете ли вы проверить правильность вашего вопроса? Вроде сам себе противоречит   -  person Caius Jard    schedule 12.09.2020
comment
Можете ли вы поделиться кодом, который вызывает исключение?   -  person Chetan Ranpariya    schedule 12.09.2020
comment
Я обнаружил несколько проблем 1, 2 по этому поводу. Похоже на ошибку CLR.   -  person Sweeper    schedule 12.09.2020
comment
@Sweeper: эти проблемы Github действительно выглядят связанными, но не совсем такими же сценариями. Тем не менее, этот комментарий ясно показывает, что такое поведение давно известно . Любопытно, что dotnetfiddle.net выдает ошибку компилятора; У меня нет более старой версии VS для тестирования, но я предполагаю, что пять лет назад (основываясь на дате страницы документа CS0523) вы получите ошибку времени компиляции вместо времени выполнения. Может быть, кто-то решил, что технически это не ошибка кода, поэтому компилятор не должен запрещать это (т.е. изменено в Roslyn).   -  person Peter Duniho    schedule 12.09.2020


Ответы (1)


Этот комментарий и еще один комментарий на Github ближе всего подходит к рассмотрению текущего положения дел, указывая, почему определение структуры не было разрешено и, вероятно, не будет разрешено в обозримом будущем.

Даже для статического члена требуется, чтобы тип был инициализирован, прежде чем его можно будет включить в макет типа, но для инициализации типа требуется, чтобы этот статический член был инициализирован. Этот цикл зависимостей инициализации создает Catch-22, который приводит к исключению во время выполнения.

Согласно этому комментарию, .NET Core отлично работает с этим шаблоном. Но я обнаружил ту же ошибку, когда попробовал ваш пример в проекте .NET Core. Так что либо этот комментарий ошибочен, либо между сценарием экземпляра-члена и вашим сценарием статического члена есть какая-то тонкая разница (я не удосужился исследовать дальше этого).

Интересно, что компилятор, используемый на dotNETFiddle.net, выдает ошибку времени компиляции, Член структуры "поле struct2" типа "struct1" вызывает цикл в макете структуры. Я не знаю, почему компилятор Visual Studio больше не выдает эту ошибку (проверено в 2017 и 2019 годах). Мне кажется еще одна ошибка. Но обсуждение этой проблемы на Github, похоже, признает, что код технически действителен (т. е. соответствует спецификации C#), поэтому, вероятно, в какой-то момент было принято сознательное решение удалить ошибку компилятора и позволить CLR подавать жалобы в время выполнения.

Обратите внимание, что совет на странице с описанием ошибки предлагает изменить на class вместо struct. Конечно, это часто невозможно, когда используется struct; может быть важно иметь тип значения. Однако в вашем конкретном примере на самом деле есть простой обходной путь, основанный на этой идее. Поскольку ваше поле не является частью фактического макета экземпляра struct, вы можете переместить его в статический класс, используемый специально для таких значений. Например.:

public struct Point<T>
{
    public static class Constants
    {
        static Point<int> IntOrigin = new Point<int>(0, 0);
    }

    T X { get; }
    T Y { get; }

    public Point(T x, T y)
    {
        this.X = x;
        this.Y = y;
    }
}

Тогда вместо (например) Point<double>.IntOrigin вам нужно будет использовать Point<double>.Constants.IntOrigin. Поскольку инициализацию типа для каждого типа можно выполнить независимо, цикл при инициализации не возникает и код работает нормально.

person Peter Duniho    schedule 12.09.2020