Возможно, ошибка компилятора C# в Visual Studio 2015

Я думаю, что это ошибка компилятора.

следующее консольное приложение компилируется и выполняется безупречно при компиляции с VS 2015:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = MyStruct.Empty;
        }

        public struct MyStruct
        {
            public static readonly MyStruct Empty = new MyStruct();
        }
    }
}

но теперь это становится странным: этот код компилируется, но он бросает!--3--> при выполнении.

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = MyStruct.Empty;
        }

        public struct MyStruct
        {
            public static readonly MyStruct? Empty = null;
        }
    }
}

вы испытываете ту же проблему? Если это так, я подам вопрос в Microsoft.

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

у меня есть методы с различными перегрузками, как

void DoSomething(MyStruct? arg1, string arg2)

void DoSomething(string arg1, string arg2)

вызов метода таким образом...

myInstance.DoSomething(null, "Hello world!")

... не компилируется.

вызов

myInstance.DoSomething(default(MyStruct?), "Hello world!")

или

myInstance.DoSomething((MyStruct?)null, "Hello world!")

работает, но выглядит некрасиво. Я предпочитаю такой способ:

myInstance.DoSomething(MyStruct.Empty, "Hello world!")

если я ставлю Empty переменной в другой класс, все работает нормально:

public static class MyUtility
{
    public static readonly MyStruct? Empty = null;
}

странное поведение, не так ли?


обновление 2016-03-29

Я открыл билет здесь:http://github.com/dotnet/roslyn/issues/10126


обновление 2016-04-06

здесь открыт новый билет:https://github.com/dotnet/coreclr/issues/4049

3 ответов


это не ошибка в 2015 году, но, возможно, ошибка языка C#. Ниже обсуждается вопрос о том, почему члены экземпляра невозможно ввести циклы, и почему Nullable<T> вызовет эту ошибку, но не должно применяться к статическим членам.

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


компиляция этого кода в VS2013 дает следующую ошибку компиляции:

член структуры 'ConsoleApplication1.Программа.MyStruct.Пустая система "типа".Nullable ' вызывает цикл в макете структуры

быстрый поиск появляется ответ в которой говорится:

незаконно иметь структуру, которая содержит себя в качестве члена.

к сожалению System.Nullable<T> тип, используемый для nullable экземпляров типов значений, также является типом значения и поэтому должен иметь фиксированный размер. Заманчиво подумать. из MyStruct? как ссылочный тип, но это действительно не так. Размер MyStruct? основано на размере MyStruct... который, по-видимому, вводит цикл в компилятор.

например:

public struct Struct1
{
    public int a;
    public int b;
    public int c;
}

public struct Struct2
{
    public Struct1? s;
}

используя System.Runtime.InteropServices.Marshal.SizeOf() вы найдете, что Struct2 имеет длину 16 байт, что указывает на то, что Struct1? это не Ссылка, а структура, которая на 4 байта (стандартный размер заполнения) длиннее, чем Struct1.


что это не происходит вот!--49-->

в ответ на ответ и комментарии Джулиуса Депуллы, вот что такое на самом деле происходит при доступе к


во-первых, при анализе этих проблем важно сделать минимальный репродуктор, чтобы мы могли сузить круг проблем. В исходном коде есть три красных селедки:readonly на static и Nullable<T>. Никто не надо репро проблему. Вот минимальный репро:

struct N<T> {}
struct M { public N<M> E; }
class P { static void Main() { var x = default(M); } }

это компилируется в текущей версии VS, но выдает исключение загрузки типа при запуске.

  • исключение не вызвано применением E. Он запускается при любой попытке получить доступ к типу M. (Как и следовало ожидать в случае исключения типа load.)
  • исключение воспроизводит, является ли поле статическим или экземпляром, только для чтения или нет; это не имеет ничего общего с природой поля. (Однако это должно быть поле! Проблема не повторяется, если это, скажем, метод.)
  • исключение не имеет ничего общего с "вызовом"; ничто не" вызывается " в минимальном репродукция.
  • исключение не имеет ничего общего с оператором доступа к члену ".". Он не появляется в минимальном repro.
  • исключение не имеет ничего общего с нулевыми; ничто не является нулевым в минимальном повторении.

теперь давайте сделаем еще несколько экспериментов. Что, если мы сделаем N и M классы? Я скажу вам результаты:

  • поведение воспроизводится только тогда, когда оба структуры.

мы могли бы продолжить обсуждение того, воспроизводится ли проблема только тогда, когда M в некотором смысле "непосредственно" упоминает себя, или "косвенный" цикл также воспроизводит ошибку. (Последнее верно.) И, как отмечает кори в своем ответе, мы также можем спросить: "должны ли типы быть общими?- Нет, есть репродуктор еще более минимальный, чем этот, без дженериков.

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

  • правило против структур, содержащих члены самих себя, здесь явно не применяется. (См. раздел 11.3.1 спецификации C# 5, которая у меня есть под рукой. Я отмечаю, что этот раздел может выиграть от тщательного переписывания с помощью дженерики в виду; некоторые из языка здесь немного неточны.) Если E является статическим, то этот раздел не применяется; если он не является статическим, то макеты N<M> и M могут быть вычислены независимо.

  • Я не знаю другого правила на языке C#, которое бы запрещало такое расположение типов.

  • это может будет случаем, что спецификация CLR запрещает это расположение типов, и CLR право сделать исключение здесь.

Итак, подведем итоги возможностей:

  • CLR имеет ошибку. Этот тип топологии должен быть законным, и это неправильно CLR бросить здесь.

  • поведение среды CLR является правильным. Этот тип топологии является незаконным, и это правильно CLR, чтобы бросить здесь. (В этом сценарии может быть так, что среда CLR имеет ошибку спецификации, в том, что этот факт не может быть адекватно поясняется в спецификации. Я не успел сделать ЦЛР спецификаций дайвинг сегодня.)

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

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

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

Подводя итог, наши возможности:

  • CLR имеет ошибку
  • спецификация C# имеет ошибку
  • реализация C# имеет ошибку
  • в программе есть ошибка

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


обновление:

эта проблема отслеживается здесь:

https://github.com/dotnet/roslyn/issues/10126

чтобы подвести итог выводам команды C# в этом выпуск:

  • программа легальна согласно обоим спецификациям CLI и C#.
  • компилятор C# 6 позволяет программе, но некоторые реализации CLI вызывают исключение загрузки типа. Это ошибка в этих реализациях.
  • команда CLR знает об ошибке,и, по-видимому, трудно исправить ошибки реализации.
  • команда C# - это учитывая создание правового кодекса предупреждение, так как он потерпит неудачу во время выполнения на некоторых, но не всех версиях CLI.

команды C# и CLR работают над этим; следите за ними. Если у вас есть какие-либо проблемы с этой проблемой, пожалуйста, напишите в проблему отслеживания, не здесь.


теперь, когда у нас была длительная дискуссия о том, что и почему, вот способ обойти проблему, не дожидаясь различных команд .NET, чтобы отследить проблему и определить, что, если что-то будет сделано об этом.

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

public struct A { public static B b; }
public struct B { public static A a; }

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

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

однако, изменив либо A или B to class проблема испаряется:

public struct A { public static B b; }
public class B { public static A a; }

таким образом, проблемы можно избежать, используя ссылочный тип для хранения фактического значения и преобразования поля в свойство:

public struct MyStruct
{
    private static class _internal { public static MyStruct? empty = null; }
    public static MyStruct? Empty => _internal.empty;
}

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

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