【问题标题】:Why are we allowed to use const with reference types if we may only assign null to them?如果我们只能将 null 分配给引用类型,为什么还允许我们使用 const 呢?
【发布时间】:2014-12-30 05:11:17
【问题描述】:

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

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

    }
}

错误:

Foo.BarBuilder' 的类型为'System.Text.StringBuilder'。一个常量字段 字符串以外的引用类型只能用 空。

MSDN 这么说,我理解并且从const 的角度来看是有道理的:

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

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

更新:

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

【问题讨论】:

  • 你是在问“你为什么要使用null?”
  • 该语言中有许多可以编译但对任何人都没有明显用途的结构(例如,密封的、空的类)。编译器不会尝试确保程序的有用性。
  • 您可以使用null 常量来声明可选参数。
  • @Tarik 这的原因。常量只是在程序中具有特殊含义的特定文字的别名。它在编译时与实际文字一起被切换出来。可以对值类型、字符串和空引用执行此操作,因为它们的值在编译时是完全已知的,因此编译器很乐意接受它们。编译器没有理由专门考虑“哦,null consts 实际上没有用”,并给你一个构建错误。
  • @Tarik 也许我误解了你的意思,但是编译器优化与这个讨论完全无关。带有或不带有优化的编译器应该符合相同的语言规范,这就是您的问题所在(为什么 C# 语言规范允许在常量中使用空值)。

标签: c# constants


【解决方案1】:

来自MSDN

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

由于需要在运行时构造引用类型(除了 null 和 are special 的字符串),因此对于引用类型,上述方法是不可能的。

对于引用类型,你能得到的最接近的是static readonly

class Foo
{
    // This is not a good idea to expose a public non-pure field
    public static readonly StringBuilder BarBuilder = new StringBuilder();
    public Foo(){
    }
}

与 const 替换(在调用代码中)不同,static readonly 创建引用类型的单个共享实例,如果程序集版本更改,则该实例具有 subtle differences

虽然不能重新分配引用 (normally),但它并不排除在 StringBuilder 上调用非纯方法(如 Append 等)。这与consts 不同,其中值类型和字符串是不可变的(可以说应该是"eternal")。

【讨论】:

    【解决方案2】:

    但是,我看不出我们使用空常量的原因或位置。

    空常量可用作sentinel values

    例如,这个:

    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)
            {
                //...
            }
        }
    }
    

    Lazy<T> 类的源代码显示 Microsoft 使用了类似的策略。尽管他们使用了一个永远不能作为哨兵值调用的静态只读委托,但他们可以只使用一个空常量来代替:

    static readonly Func<T> ALREADY_INVOKED_SENTINEL = delegate
    {
        Contract.Assert(false, "ALREADY_INVOKED_SENTINEL should never be invoked.");
        return default(T);
    };
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 感谢 Avner,但我不认为这会导致这么多问题,因为编译器已经可以将字符串与其他引用类型区分开来。最困难的部分是字符串和其他引用类型之间的这种特殊处理,但编译器已经处理了它。我要问的应该没那么难。
      • 这并不难,但它使语言更复杂。最好语言简单,编译器复杂
      • 它实际上是通过使语言更加一致而使语言更简单。编译器已经够复杂了。
      【解决方案4】:

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

      我认为你是对的,它没有多大意义,但如果你设计了自己的库并且如果你想与 null 比较但想赋予特殊含义(比如只与你的库值比较而不是直接为空)

      public class MyClass
          {
              public const MyClass MyClassNull = null;
              public MyClass()
              {
              }
          }
      

      它的用法是这样的。

      object obj = GetMyClass();
      if(obj == MyClass.MyClassNull) // This going to convert to actual null in MSIL.
      {    
      }
      

      【讨论】:

        猜你喜欢
        • 2022-12-23
        • 2023-04-02
        • 1970-01-01
        • 1970-01-01
        • 2021-11-01
        • 2011-12-15
        • 2021-08-03
        • 1970-01-01
        • 2013-05-08
        相关资源
        最近更新 更多