在方法中使用const而不是变量的优点

问题描述 投票:68回答:8

每当我在方法中有局部变量时,ReSharper建议将它们转换为常量:

// instead of this:
var s = "some string";
var flags = BindingFlags.Public | BindingFlags.Instance;

// ReSharper suggest to use this:
const string s = "some string";
const BindingFlags flags = BindingFlags.Public | BindingFlags.Instance;

鉴于这些是真正的常量值(而不是变量)我理解ReSharper建议将它们更改为const。

但除此之外,使用const(例如更好的性能)是否有任何其他优势,这证明使用const BindingFlags而不是方便可读的var关键字?

顺便说一句:我刚刚在这里找到了一个类似的问题:Resharper always suggesting me to make const string instead of string,但我认为它更多的是关于一个类的字段,我的问题是关于局部变量/ consts。

c# coding-style resharper const
8个回答
82
投票

如果您尝试将值分配给常量,编译器将抛出错误,从而可能会阻止您意外更改它。

此外,通常使用常量与变量有一个小的性能优势。这与它们编译到MSIL的方式有关,每个this MSDN magazine Q&A

现在,无论在代码中引用myInt,而不是必须执行“ldloc.0”来从变量中获取值,MSIL只需加载硬编码到MSIL中的常量值。因此,使用常量通常具有较小的性能和内存优势。但是,为了使用它们,您必须在编译时具有变量的值,并且在编译时对该常量的任何引用,即使它们位于不同的程序集中,也会进行此替换。

如果在编译时知道值,常量肯定是一个有用的工具。如果不这样做,但想确保您的变量只设置一次,则可以使用C#中的readonly关键字(映射到MSIL中的initonly)来指示变量的值只能在构造函数中设置;在那之后,改变它是一个错误。这通常在字段有助于确定类的标识时使用,并且通常设置为等于构造函数参数。


20
投票

tl; dr用于具有文字值的局部变量,const完全没有区别。


你对“内部方法”的区分非常重要。让我们看看它,然后将它与const字段进行比较。

Const local variables

const局部变量的唯一好处是无法重新分配该值。

然而,const仅限于原始类型(intdouble,...)和string,这限制了它的适用性。

题外话:有人建议C#编译器允许更一般的'readonly'本地化概念(here),这会将这种好处扩展到其他场景。它们可能不会被认为是const,并且可能会有这样的声明的不同关键字(即letreadonly var或类似的东西)。

考虑这两种方法:

private static string LocalVarString()
{
    var s = "hello";
    return s;
}

private static string LocalConstString()
{
    const string s = "hello";
    return s;
}

建立在Release模式,我们看到以下(删节)IL:

.method private hidebysig static string LocalVarString() cil managed 
{
    ldstr        "hello"
    ret          
}

.method private hidebysig static string LocalConstString() cil managed 
{
    ldstr        "hello"
    ret          
}

如您所见,它们都产生完全相同的IL。当地的s是否是const没有影响。

原始类型也是如此。这是使用int的示例:

private static int LocalVarInt()
{
    var i = 1234;
    return i;
}

private static int LocalConstInt()
{
    const int i = 1234;
    return i;
}

IL再次:

.method private hidebysig static int32 LocalVarInt() cil managed
{
    ldc.i4       1234
    ret          
}

.method private hidebysig static int32 LocalConstInt() cil managed
{
    ldc.i4       1234
    ret     
}

所以我们再次看到没有区别。这里不存在性能或内存差异。唯一的区别是开发人员无法重新分配符号。

Const fields

const字段与可变字段进行比较是不同的。必须在运行时读取非const字段。所以你最终得到这样的IL:

// Load a const field
ldc.i4       1234

// Load a non-const field
ldsfld       int32 MyProject.MyClass::_myInt

很明显,假设JIT本身不能内联一个常量值,这会导致性能差异。

这里的另一个重要区别是跨程序集共享的公共const字段。如果一个程序集公开const字段,而另一个程序集使用它,则在编译时复制该字段的实际值。这意味着如果更新包含const字段的程序集但不重新编译using程序集,则将使用旧的(可能不正确的)值。

Const expressions

考虑这两个声明:

const int i = 1 + 2;
int i = 1 + 2;

对于const形式,必须在编译时计算加法,这意味着数字3保存在IL中。

对于非const形式,编译器可以自由地在IL中发出加法运算,尽管JIT几乎肯定会应用基本的常量折叠优化,因此生成的机器代码将是相同的。

C# 7.3 compiler emits the ldc.i4.3 opcode用于上述两种表达方式。


14
投票

根据我的理解,Const值在运行时不存在 - 即以存储在某些存储器位置的变量的形式 - 它们在编译时嵌入在MSIL代码中。因此会对性能产生影响。在变量需要这些检查的情况下,不需要更多的运行时间来执行任何内容保存(转换检查/垃圾收集等)。


4
投票

const是一个编译时常量 - 这意味着所有使用const变量的代码都被编译为包含const变量包含的常量表达式 - 发出的IL将包含该常量值本身。

这意味着您的方法的内存占用量较小,因为该常量不需要在运行时分配任何内存。


3
投票

除了小的性能改进之外,当您声明一个常量时,您明确地对自己和将使用您的代码的其他开发人员实施两个规则

  1. 我必须用一个值初始化它现在我不能在任何其他地方做它。
  2. 我不能在任何地方改变它的价值。

代码中的所有关于可读性和通信。


2
投票

const值也在对象的所有实例之间“共享”。它也可能导致内存使用率降低。

举个例子:

public class NonStatic
{
    int one = 1;
    int two = 2;
    int three = 3;
    int four = 4;
    int five = 5;
    int six = 6;
    int seven = 7;
    int eight = 8;
    int nine = 9;
    int ten = 10;        
}

public class Static
{
    static int one = 1;
    static int two = 2;
    static int three = 3;
    static int four = 4;
    static int five = 5;
    static int six = 6;
    static int seven = 7;
    static int eight = 8;
    static int nine = 9;
    static int ten = 10;
}

在.Net中内存消耗是棘手的,我不会假装理解它的细节,但如果你实例化一个百万'静态'的列表,它可能会使用比你没有更少的内存。

    static void Main(string[] args)
    {
        var maxSize = 1000000;
        var items = new List<NonStatic>();
        //var items = new List<Static>();

        for (var i=0;i<maxSize;i++)
        {
            items.Add(new NonStatic());
            //items.Add(new Static());
        }

        Console.WriteLine(System.Diagnostics.Process.GetCurrentProcess().WorkingSet64);
        Console.Read();
    }

当使用'NonStatic'时,工作集为69,398,528,而使用静态时仅为32,423,936。


1
投票

const关键字告诉编译器它可以在编译时完全评估。这有一个性能和内存优势,但它很小。


1
投票

C#中的常量在内存中提供一个命名位置来存储数据值。这意味着变量的值将在编译时知道,并将存储在一个地方。

当您声明它时,它在Microsoft中间语言(MSIL)中是一种“硬编码”。

虽然有点,但它可以提高代码的性能。如果我声明一个变量,我可以使它成为一个常量,我总是这样做。不仅因为它可以提高性能,还因为它是常量的概念。否则,它们为什么存在?

反射器在这样的情况下非常有用。尝试声明变量然后使其成为常量,并查看IL中生成的代码。然后,您需要做的就是查看说明中的差异,并查看这些说明的含义。

© www.soinside.com 2019 - 2024. All rights reserved.