C# 如果我们只能为引用类型分配null,为什么我们可以将const与引用类型一起使用?

C# 如果我们只能为引用类型分配null,为什么我们可以将const与引用类型一起使用?,c#,constants,C#,Constants,这个问题其实很简单。以下代码在其正下方引发异常: class Foo { public const StringBuilder BarBuilder = new StringBuilder(); public Foo(){ } } 错误: Foo.BarBuilder的类型为System.Text.StringBuilder。常数域 只能使用初始化字符串以外的引用类型的 空 MSDN是这样说的,我理解这一点,从const的角度来看,这是有道理的: 常量表达式是一个可以

这个问题其实很简单。以下代码在其正下方引发异常:

class Foo
{
    public const StringBuilder BarBuilder = new StringBuilder();
    public Foo(){

    }
}
错误:

Foo.BarBuilder的类型为System.Text.StringBuilder。常数域 只能使用初始化字符串以外的引用类型的 空

MSDN是这样说的,我理解这一点,从
const
的角度来看,这是有道理的:

常量表达式是一个可以在 编译时。因此,常数的唯一可能值为 引用类型为字符串和空引用

但是,我看不出为什么或者在哪里使用
null
常量。那么,首先为什么引用类型(字符串除外)可以用
const
定义,如果它只能设置为
null
,并且如果这是一个深思熟虑的决定(我相信是这样),那么我们在哪里可以使用带有null值的常量呢

更新:

当我们想到一个答案时,请让我们以不同于“我们有这个,为什么没有那个…”上下文的方式思考

当编译器在C#源代码中遇到常量标识符(例如,months)时,它会将文本值直接替换为它生成的中间语言(IL)代码。因为在运行时没有与常量关联的变量地址,所以常量字段不能通过引用传递,也不能在表达式中显示为l值

因为需要在运行时构造引用类型(null和字符串除外),所以对于引用类型,上述操作是不可能的

对于引用类型,您可以获得的最接近的值是:

与常量替换(在调用代码中)不同,
static readonly
创建引用类型的单个共享实例,如果程序集版本发生更改,则该引用类型将发生更改


尽管无法重新分配引用(),但它并不排除在
StringBuilder
上调用非纯方法(如
Append
等)。这不同于
常量
,其中值类型和字符串是不可变的(可以说应该是不可变的)。

正如您在问题中所述,有一种引用类型可以放入
常量
引用-字符串中。编译器会对此进行特殊处理,并将字符串放入已编译的输出中,并允许在运行时将其读入引用类型


当然,这就引出了一个问题——为什么不让字符串成为唯一可以是
const
的引用类型,只要我们对它们进行特殊的大小写呢?对此,我只能推测,在编译器中添加一个特例比在语言中添加一个特例更简单,问题更少。从语言的角度来看,字符串只是一种引用类型,即使编译器有特殊的处理方法来从字符串文本和编译的资源创建它的实例。

我想您是在问为什么带有null的引用类型允许作为常量

我认为您是对的,它没有多大意义,但如果您设计了自己的库,并且希望与null进行比较,但希望赋予特殊含义(例如仅与库值进行比较,而不是直接与null进行比较),那么它是有用的

是这样的

object obj = GetMyClass();
if(obj == MyClass.MyClassNull) // This going to convert to actual null in MSIL.
{    
}
然而,我不明白为什么或者在哪里使用空常量

空常量在以下情况下很有用

例如,这:

public class MyClass
{
    private const Action AlreadyInvoked = null;

    private Action _action;

    public MyClass(Action action) {
        _action = action;
    }

    public void SomeMethod()
    {
        _action();

        _action = AlreadyInvoked;
    }

    public void SomeOtherMethod()
    {
        if(action == AlreadyInvoked)
        {
            //...
        }
    }
}
比这更具表现力:

public class MyClass
{
    //...

    public void SomeMethod()
    {
        _action();

        _action = null;
    }

    public void SomeOtherMethod()
    {
        if(action == null)
        {
            //...
        }
    }
}
该类的源代码显示Microsoft使用了类似的策略。尽管他们使用了一个永远不能作为sentinel值调用的静态只读委托,但他们可以只使用null常量:

static readonly Func<T> ALREADY_INVOKED_SENTINEL = delegate
{
    Contract.Assert(false, "ALREADY_INVOKED_SENTINEL should never be invoked.");
    return default(T);
};
static readonly Func已被调用\u SENTINEL=delegate
{
Assert(false,“已经调用了”\u SENTINEL不应该被调用);
返回默认值(T);
};

您是否在问“为什么要使用
null
?”该语言中有许多构造将被编译,但对任何人都没有明显的实用性(例如,密封的空类)。编译器不会试图确保程序的有用性。您可以使用
null
常量来声明可选参数。@Tarik这就是它的原因。常量只是在程序中具有特殊含义的特定文字的别名。它在编译时被转换为实际的文本。对于值类型、字符串和空引用可以这样做,因为它们的值在编译时是完全已知的,所以编译器很乐意接受它们。编译器没有理由特别考虑“哦,空常量实际上没有用处”,并给您一个生成错误。@塔里克,也许我误解了您的意思,但编译器优化与本次讨论完全无关。有优化或没有优化的编译器都应该符合相同的语言规范,这就是你的问题所在(为什么C语言规范允许常量中有空值)。谢谢Avner,但我认为这不会造成太多问题,因为编译器已经能够区分字符串和其他引用类型。最困难的部分是字符串和其他引用类型之间的特殊处理,但编译器已经处理了它。我要问的不应该那么难,不难,但会让语言变得更复杂。最好保持语言的简单性,编译器的复杂性实际上是通过使语言更加一致而使语言更简单。编译器已经足够复杂了。
public class MyClass
{
    //...

    public void SomeMethod()
    {
        _action();

        _action = null;
    }

    public void SomeOtherMethod()
    {
        if(action == null)
        {
            //...
        }
    }
}
static readonly Func<T> ALREADY_INVOKED_SENTINEL = delegate
{
    Contract.Assert(false, "ALREADY_INVOKED_SENTINEL should never be invoked.");
    return default(T);
};