8

阅读 Jon Skeet 的书,我发现(现在有一段时间)在函数调用中使用了“命名参数”。这是一个快速简单的示例:

void Dump(int x, int y, int z, string cSomeText)
{
    // no use, just to see how we call this later
    string cOnMe = string.Format("[{0}] [{1}] [{2}] [{3}]", x, y, z, cSomeText);
}

void CallDumpWithoutNameArguments()
{
    // call with out Name Arguments
    Dump(1, 2, 3, "Test string");
}

void CallDumpWithNameArguments()
{ 
    // more easy to read, call the same function with Name Arguments
    Dump(x: 1, y: 2, z: 3, cSomeText: "Test string");
}

使用之后,后来看到编译后的代码,我看到这个名字的使用实际上是在函数调用之前创建了变量。

这是创建的代码:

private void CallDumpWithoutNameArguments()
{
    this.Dump(1, 2, 3, "Test string");
}

private void CallDumpWithNameArguments()
{
    int CS$0$0000 = 1;
    int CS$0$0001 = 2;
    int CS$0$0002 = 3;
    string CS$0$0003 = "Test string";
    this.Dump(CS$0$0000, CS$0$0001, CS$0$0002, CS$0$0003);
}

和完整的编译代码,你会看到我用“命名参数”来称呼它有多大

  .method private hidebysig instance void CallDumpWithoutNameArguments()
  {
    .maxstack 8
                nop
                ldarg.0
                ldc.i4.1
                ldc.i4.2
                ldc.i4.3
                ldstr    "Test string"
                call     instance void SubSonic.BabisExtrasNoUseIt.ExtraTestCode::Dump(int32 x, int32 y, int32 z, string cSomeText)
                nop
                ret
  }

  .method private hidebysig instance void CallDumpWithNameArguments()
  {
    .maxstack 5
    .locals init (int32 V0,
                  int32 V1,
                  int32 V2,
                  string V3)
                nop
                ldarg.0
                ldc.i4.1
                stloc.0
                ldc.i4.2
                stloc.1
                ldc.i4.3
                stloc.2
                ldstr    "Test string"
                stloc.3
                ldloc.0
                ldloc.1
                ldloc.2
                ldloc.3
                call     instance void SubSonic.BabisExtrasNoUseIt.ExtraTestCode::Dump(int32 x, int32 y, int32 z, string cSomeText)
                nop
                ret
  }

所以这是c#忘记优化的一点,或者还有其他用途吗?

跟进

我想明确一点,上面的代码是编译产生的。这是我从 Servy 得到的答案。

private void CallDumpWithoutNameArguments()
{
    // what generated from
    // int i = 0;
    // Dump(i++, i++, i++, cSomeText: "Test string");

    int i = 0;
    string CS$0$0000 = "Test string";
    this.Dump(i++, i++, i++, CS$0$0000);
} 

private void CallDumpWithNameArguments()
{
    // what is generate from
    // int i = 0;
    // Dump(x: i++, z: i++, y: i++, cSomeText: "Test string");

    int i = 0;
    int CS$0$0000 = i++;
    int CS$0$0001 = i++;
    int CS$0$0002 = i++;
    string CS$0$0003 = "Test string";
    this.Dump(CS$0$0000, CS$0$0002, CS$0$0001, CS$0$0003);
}
4

1 回答 1

8

这与确保代码以正确的顺序运行有关。对于命名参数,每个参数的表达式需要按照它们在源代码中出现的顺序执行,而不是它们在定义的实际参数列表中出现的顺序。想象一下(非常卑鄙的)电话:

int i = 0;
Dump(x: i++,  z: i++, y: i++, cSomeText: "Test string");

它最终是否应该与以下内容相同:

Dump(0, 1, 2, "Test string");

或者

Dump(0, 2, 1, "Test string");

如果每个命名参数都没有局部变量,那么您最终会得到第一个,如果有,则最终会得到第二个。

看起来,与其试图确定是否有必要创建局部变量(参数是否无序,它们是否会导致副作用,那些副作用对其他参数中的表达式是否可见),而是总是创建更简单局部变量。已经多次声明 C# 编译器将确保优化的正确性;它为 JIT 留下了优化。

于 2012-09-11T19:31:26.397 回答