在C#中用类型推断一个常量

时间:2010-01-24 19:33:35

标签: c# constants type-inference

在C#中,以下类型推断有效:

var s = "abcd";

但是当变量是常数时,为什么不能推断出类型?

以下命令抛出编译时异常:

const var s = "abcd"; // <= Compile time error: 
                      //    Implicitly-typed local variables cannot be constant

11 个答案:

答案 0 :(得分:31)

  

我实际上希望Lippert会出现并且看看问题

如果你想引起我的注意,你可以把我的名字留在文本中 - 而不是评论 - 我最终会找到它。或者,更好的是,您可以“鸣叫”到@ericlippert。请注意,这不构成服务级别协议;我在业余时间这样做。

  

为什么在变量是常量时不能推断出类型?

“常数”和“变量”是对立const var让我感到不寒而栗。常量是一个永远不会改变且没有存储位置的值;变量是其内容发生变化的存储位置。它们完全不同,所以不要试图将它们结合起来。选择var语法来调出“这是一个变量”,我们坚持使用它。

var可以代表一个特定的类型声明,但是将它与const严重混淆了编译器所做的的图片。因此,不允许使用const var来防止这种混淆,您必须明确键入常量。

对于不使用var的推断常量,我会完全没问题:

const Pi = 3.14159;

对我来说似乎很好。但是,我知道没有计划将其添加到C#。

答案 1 :(得分:12)

这只是一个猜测,但我认为原因可能与const值在编译时放入元数据(它具有微妙的后果)这一事实有关。我想知道编译器是否有一些问题可以找出如何将var转换为元数据。

在Richter的 CLR VIA C#(第177页)中,

  

定义常量会导致创建   元数据。当代码引用a时   常数符号,编译器查找   该元数据中的符号   定义该常量的程序集,   提取常量的值,然后   将值嵌入发出的IL中   代码。

他接着指出,这意味着由于这个原因你无法获得对常量的记忆的引用。为了使它更加明确,在psuedo C#中,如果程序集A定义了一个const:

//Assembly A, Class Widget defines this:
public static const System.Decimal Pi = 3.14

然后你有A:

的消费者
//somewhere in the Program.exe assembly
decimal myCircleCurcum = 2 * Widget.pi

program.exe的编译IL会像这样的伪代码执行:

// pseudo-IL just to illustrate what would happen to the const
myCircleCurcum = 2*3.14

请注意,消费程序集根本不知道小数3.14与程序集A有任何关系 - 它是program.exe的文字值。对我而言,对于C#编译器来说,这是一种合理的方式 - 毕竟,程序集A 明确声明,pi是一个常量(意味着值是一次且对于所有pi = 3.14) 。但是,我冒昧地猜测,99%的C#开发人员并不了解这个&amp; amp;的结果。可能会随心所欲地将pi改为3.1415。

常量具有非常差的跨组件版本故事(同样,这来自Richter),因为如果程序集A的常量发生变化(即重新编译),组件A中具有常量的消费者将不会看到更改。这可能导致很难找出组件A的消费者的错误。 。所以我禁止我的团队使用常量。他们的轻微性能增加不值得他们可能导致的微妙错误。

你真的只能使用一个常数,如果你知道这个值永远不会改变 - 甚至设置为像pi这样的const,你也不能肯定地说你不会希望你的percision在将来改变。

如果程序集A定义:

decimal const pi = 3.14

然后你构建它,然后其他程序集使用它,如果你然后更改程序集A:

decimal const pi = 3.1415

并重建程序集A,程序集A的使用者仍将具有旧值3.14!为什么?因为原始3.14被定义为常量,这意味着程序集A的消费者被告知该值不会改变 - 因此他们可以将pi的值烘焙到他们自己的元数据中(如果你重建了程序集A的消费者)然后将在其元数据中获得pi的新值。同样,我不认为这是CSC处理常量的方式的一个问题 - 只是开发人员可能不希望在某些情况下不能安全地改变 在其他人可以安全地改变。安全:任何消费者都不会仅通过.dll引用(即它们将始终从源构建每个时间),不安全:消费者对于使用const定义它的程序集的源代码何时更改时没有任何线索。在.NET文档中可能应该更加清楚常量意味着您无法更改源代码中的值

出于这个原因,我强烈建议不要使用常量,而只是简单地使小部件。你能真正说出多少值真正永远是永恒的?

在我的脑海中使用const而不是readonly的唯一真正原因是,某些东西可能会对性能产生影响......但如果你遇到这种情况,我会怀疑C#是否真的是你问题的正确语言。简而言之,对我而言,使用常量绝不是一个好主意。极少数情况下,微小的性能改善值得潜在的问题。

答案 2 :(得分:11)

我同意埃里克的观点,认为这很丑陋:

const var s = "abcd"

但为什么不简单呢?

const s = "abcd"

对我来说似乎是一种合理的语法。

答案 3 :(得分:7)

简短的回答是因为语言设计师(微软)这么说。

来自MSDN

  

编译器错误CS0822

     

错误消息:隐式输入的本地人   不能是const

     

隐式输入的局部变量是   只需要存储匿名   类型。在所有其他情况下,他们是   只是方便。如果值   变量永远不会改变,只需给出   它是一个明确的类型。试图使用   readonly修饰符   隐式输入本地将生成   CS0106。

     

更正此错误

     

如果您要求变量保持不变或只读,请给它一个   显式类型。

答案 4 :(得分:3)

我不同意@Eric。

var 关键字并不意味着“这是一个变量”,它意味着“要推断出类型”。

int,long等是否是识别变量的“关键字”?不,它们只是数据类型,可以用于变量或常量。

我认为关键字 var 的名称被认为类似于Javascript,我觉得不合适。

自动怎么样? (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1705.pdf

答案 5 :(得分:3)

我的回答?由于目前无法使用“const var”,所以甚至不用担心它。这种限制,完全没有任何理由,使得C#在处理常量与变量的方式上不平衡,从而产生了不对称性。你会好起来的

“选择”var“语法来调用”这是一个变量“,我们坚持使用它。”

我发现Eric Lippert的争论在多个层面上都非常难以理解。

Eric,我不知道“我们”是谁,我真的不想听起来粗鲁但是使用(因为存在的原因)和意思< / strong>(因为var为什么是合适的名称)与你试图附加到它的含义无关。 “Var”是使用它是一个显式类型声明的位置,表示它的类型,在那个时间点,可以是众多之一。

总结一下,var取代了类型声明。让我们不假装它做任何其他事情,因为类型和值(以及这个值是否可以改变)是两个截然不同的事情。 Occum的剃刀适用于此,并且不需要扩展var的含义超出它的含义。

更重要的是,即使在隐式声明不是一个选项并且var关键字正在使用的日子里,人们仍然认为它们的对象是变量,并且没有问题将它们的变量声明为常量。

引入“var”因为需要它。而这种需求并不是为了使变量不会成为常量。有限的interpritation产生了另一种需求,目前尚未满足。

你的整个立场可以推断出一个语法论点 - 我们根本不喜欢“const var sounds”的方式(例如“给我颤抖的类型。”)这很奇怪,因为人们可以输入“动态静态“没有编译错误,听起来也很尴尬。

为什么要强调一开始就没有任何风险的东西?是“const var =”Hello World“”还是其中的一些变化真的会让人们感到困惑,天气是不变的。我认为人们将能够准确理解这意味着什么,就像他们理解“动态静态”的含义一样。

真正的底线是能够隐式声明常量既有意义又可以实际有用。目前似乎没有理由,目前无法做到这一点。能够声明“const var”比引入另一个关键字来提供隐式声明的常量更有意义。

如果你不认为Eric的论证完全是基于对语义的不必要的复杂解释,那么如果用不同的名字调用,那么试着围绕“var”的含义构建相同的参数。 。说,impl。是否有任何理由为什么impl不能与const一起使用?我很难想出一个理由。因此,它归结为不喜欢“const var”声音的方式,而不是别的。我想我们大多数人都可以轻易克服这一点。

答案 6 :(得分:2)

在这种情况下,很明显你知道引用类型是常量的,并且是一个相当原始的类型(consts只能是值类型,或字符串等等),所以你应该声明那个类型,而不是使用隐式输入。

换句话说,因为类型显然是常数且已知,所以绝对没有理由使用var。

  

隐式输入的局部变量是   只需要存储匿名   类型。在所有其他情况下,他们是   只是方便。如果值   变量永远不会改变,只需给出   它是一个明确的类型。试图使用   readonly修饰符   隐式输入本地将生成   CS0106。

http://msdn.microsoft.com/en-us/library/bb310881.aspx

编译器错误CS0822

  

如果需要,请更正此错误   变量是常数或   readonly,给它一个明确的类型。

答案 7 :(得分:2)

虽然我不同意Lippert先生的推理,但有充分理由不允许隐式输入命名常量:考虑以下代码的含义,如果类型常量不必明确指定其类型:

const var ScaleFactor = 2500000000; // Type 'Int64'

...
int thisValue = getNextInt();
total += thisValue * ScaleFactor;

现在假设比例因子需要减少20%。将值更改为2000000000会产生什么影响?虽然在代码中指定了值,但是Int64成为Int32的问题会发生[例如,将total += thisValue * 2500000000;更改为total += thisValue * 2000000000;时,更改将与要求值为Int64的代码相邻。相比之下,const声明可能会远离它所影响的代码,因此没有可见的方法来了解某处的代码是否可能依赖于常量为long类型。

答案 8 :(得分:1)

有趣。我不知道它是否只是C#编译器的限制,或者它是语言本身的有趣限制。

要解释我的意思,请考虑VB。

在VB 9中,你也无法推断常量,但这只是编译器的限制。 在VB 10中,他们能够添加常量类型推断,而无需对语言进行任何重大更改。

答案 9 :(得分:0)

IMO var的主要目的是允许匿名类型(类型未知,使用var可以声明一个变量来存储它)。现在更常见的用法是编写更少的代码;)。 如果您知道类型和值(不会改变),他们会解释here,只需写出类型。

答案 10 :(得分:0)

我意识到我(我们)实际想要的是JavaScript或C中定义的const关键字的行为。也就是说,它能够在运行时计算它,但不允许在后续代码中更新它。当你只想要计算一次值时,这对于强制纪律和明确是有用的。

换句话说,这个问题的真正要求是能够在本地使用readonly关键字(变量/方法参数)。即,这种语法可能很有用:

// This variable should never be overwritten!
readonly var target = 4;

这并不像C#中没有先例。 using()和迭代(foreach)变量已经以这种方式运行:

class Program
{
    static void Main(string[] args)
    {
        foreach (var x in new[] { "asdf", })
        {
            System.Console.WriteLine(x);
            // error CS1656: Cannot assign to 'x' because it is a 'foreach iteration variable'
            x = "food";
        }
    }
}

哦,看 - 我得到了类型推断 readonly行为!好极了!但是,使用foreach关键字实际上在实际代码中实际执行此操作过于笨拙。根本没有明显的是,你试图保护自己,呃,你自己或你的同事添加的代码在不考虑影响的情况下(在编译器错误的帮助下)改变x。这就是it would be great if it became a language feature

的原因