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

本文关键字:变量 方法 const | 更新日期: 2023-09-27 17:54:21

每当方法中有局部变量时,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关键字?

BTW:我刚刚在这里发现了一个类似的问题:Resharper总是建议我用const string代替string,但我认为它更多的是关于类的字段,我的问题是关于局部变量/const。

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

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

而且,通常使用常量和变量在性能上有一点优势。这与它们被编译成MSIL的方式有关,根据这个MSDN杂志Q&A:

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

如果在编译时知道常量的值,那么

常量无疑是一个有用的工具。如果你不这样做,但想确保你的变量只被设置一次,你可以使用c#中的readonly关键字(在MSIL中映射为initonly)来指示变量的值只能在构造函数中设置;在那之后,改变它是错误的。当字段帮助确定类的标识时,通常使用此方法,并且通常设置为等于构造函数参数。

tl;dr对于具有文字值的局部变量,const对其没有影响。


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

Const局部变量

const局部变量的唯一的好处是不能重新赋值。

但是const被限制为基本类型(int, double,…)和string,这限制了它的适用性。

题外话:有人建议c#编译器允许一个更通用的"只读"局部概念(这里),这将把这个好处扩展到其他场景。它们可能不会被认为是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字段与变量字段不同的。非常量字段必须在运行时读取。所以你最终会得到这样的IL:

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

如果JIT本身不能内联常量值,那么很明显这会导致性能差异。

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

常量表达式

考虑以下两个声明:

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

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

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

c# 7.3编译器为上述两个表达式发出ldc.i4.3操作码。

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

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

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

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

  1. 我现在必须用一个值初始化它,我不能在其他任何地方这样做。
  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'的列表,它可能会比你不实例化的情况下使用更少的内存。

    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();
    }

当使用"非静态"时,工作集是69,398,528,而使用静态时只有32,423,936。

const关键字告诉编译器可以在编译时完全求值。有一场演出&;这对内存有好处,但它很小。

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

当你声明它时,它是在微软中间语言(MSIL)中"硬编码"的。

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

Reflector在这种情况下非常有用。尝试声明一个变量,然后将其变为常量,看看在IL中生成了什么代码。然后你所需要做的就是看看指令之间的区别,看看这些指令是什么意思。