可能是Visual Studio 2015中的C#编译器错误

可能是Visual Studio 2015中的C#编译器错误,c#,visual-studio-2015,roslyn,compiler-bug,coreclr,C#,Visual Studio 2015,Roslyn,Compiler Bug,Coreclr,我认为这是一个编译器错误 使用VS 2015编译时,以下控制台应用程序可以完美地编译和执行: namespace ConsoleApplication1 { class Program { static void Main(string[] args) { var x = MyStruct.Empty; } public struct MyStruct {

我认为这是一个编译器错误

使用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();
        }
    }
}
但现在它变得奇怪了:这段代码可以编译,但执行时会抛出
TypeLoadException

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,字符串arg2)

void DoSomething(字符串arg1、字符串arg2)

以这种方式调用方法

myInstance.DoSomething(null,“Hello world!”)

。。。不编译

召唤

myInstance.DoSomething(默认值(MyStruct?)“你好,世界!”)

myInstance.DoSomething((MyStruct?)null,“你好,世界!”)

工作正常,但看起来很难看。我更喜欢这样:

myInstance.DoSomething(MyStruct.Empty,“你好,世界!”)

如果我将
Empty
变量放入另一个类中,则一切正常:

public static class MyUtility
{
    public static readonly MyStruct? Empty = null;
}
奇怪的行为,不是吗


更新2016-03-29 我在这里开了一张票:


更新2016-04-06
在这里新开了一张罚单:

这不是2015年的一个bug,但可能是一个C语言bug。下面的讨论涉及到为什么实例成员不能引入循环,以及为什么
Nullable
将导致此错误,但不应应用于静态成员

我会把它作为语言错误提交,而不是编译器错误


在VS2013中编译此代码会出现以下编译错误:

类型为“System.Nullable”的结构成员“ConsoleApplication1.Program.MyStruct.Empty”在结构布局中导致循环

快速搜索将显示以下状态:

将自身作为成员的结构包含在内是不合法的

不幸的是,用于值类型的可空实例的
System.Nullable
类型也是值类型,因此必须具有固定大小。人们很容易将
MyStruct?
视为引用类型,但事实并非如此。
MyStruct?
的大小基于
MyStruct
的大小。。。这显然在编译器中引入了一个循环

例如:

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

public struct Struct2
{
    public Struct1? s;
}
public struct A { public static B b; }
public struct B { public static A a; }
使用
System.Runtime.InteropServices.Marshal.SizeOf()
您会发现
Struct2
的长度为16字节,这表明
Struct1?
不是引用,而是比
Struct1
长4字节(标准填充大小)的结构


这里发生了什么事 作为对Julius Depulla的回答和评论的回应,下面是当您访问
静态可空
字段时实际发生的情况。根据该代码:

public struct foo
{
    public static int? Empty = null;
}

public void Main()
{
    Console.WriteLine(foo.Empty == null);
}
以下是从LINQPad生成的IL:

IL_0000:  ldsflda     UserQuery+foo.Empty
IL_0005:  call        System.Nullable<System.Int32>.get_HasValue
IL_000A:  ldc.i4.0    
IL_000B:  ceq         
IL_000D:  call        System.Console.WriteLine
IL_0012:  ret         
IL_0000:ldsflda UserQuery+foo.Empty
IL_0005:调用System.Nullable.get_HasValue
IL_000A:ldc.i4.0
IL_000B:ceq
IL_000D:call System.Console.WriteLine
IL_0012:ret
第一条指令获取静态字段
foo.Empty
的地址,并将其推送到堆栈上。由于
可为空
是一种结构而不是引用类型,因此保证此地址不为空

接下来调用
Nullable
隐藏成员函数
get\u HasValue
,以检索
HasValue
属性值。这不会导致空引用,因为如前所述,值类型字段的地址必须为非空,而与地址中包含的值无关

其余的只是将结果与0进行比较,并将结果发送到控制台


在这个过程中,任何时候都不可能“在类型上调用null”,不管这意味着什么。值类型没有空地址,因此对值类型的方法调用不能直接导致空对象引用错误。这就是为什么我们不称它们为引用类型。

首先,在分析这些问题时,重要的是要制作一个最小的复制器,以便我们能够缩小问题的范围。在原始代码中有三个危险因素:
只读
静态
可空
。没有必要对这个问题进行重新解释。这里有一个小程序:

struct N<T> {}
struct M { public N<M> E; }
class P { static void Main() { var x = default(M); } }
struct N{}
结构M{public N E;}
类P{static void Main(){var x=default(M);}
这将在当前版本的VS中编译,但在运行时引发类型加载异常

  • 使用
    E
    不会触发异常。任何试图访问类型
    M
    的尝试都会触发该命令。(正如在类型加载异常的情况下所期望的那样。)
  • 异常再现字段是静态还是实例,只读与否;这与该领域的性质无关。(但是,它必须是一个字段!如果它是(比如)一个方法,则问题不会重新出现。)
  • 例外与“调用”无关;在最小复制中没有“调用”任何内容
  • 异常与成员访问操作符“”无关。它不会出现在最小复制中
  • 例外情况与可空项没有任何关系;最小复制中没有可为空的内容
现在让我们做更多的实验。如果我们制作
N
M
类呢?我将告诉你结果:

  • 只有当两者都是结构时,行为才会复制
我们可以继续讨论这个问题是否只在M在某种意义上“直接”提到它自己时才重现,或者“间接”循环是否也重现了这个bug。(后者是正确的。)正如Corey在他的回答中指出的,我们也可以问“类型必须是泛型的吗?”不;有一个复制器甚至比这个更小,没有泛型

但是我认为我们有足够的钱来补偿
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;
}