Dzisiaj zaczynam pierwszy wpis o nowościach w C# 7.0. Przede wszystkim warto ściągnąć Visual Studio 15 Preview. Wersja 15, to przyszły następca Visual Studio 2015, który określany był wersją 14.
Jeśli chcemy sprawdzić nowości z C# 7.0 musimy najpierw ustawić __DEMO__ oraz __DEMO_EXPERIMENTAL__ we właściwościach projektu:

Pierwsza nowość to zagnieżdżone funkcję lokalne. Przykład:
public class Test
{
public void DoSomething()
{
int GetValue(int a)
{
return a;
}
Console.WriteLine(GetValue(5));
}
}
Innymi słowy jest to funkcja w funkcji. Czasami deklarujemy prywatną metodę, która używana jest jedynie w jednej metodzie jako helper. Za pomocą lokalnej funkcji w łatwy sposób możemy ograniczyć jej zasięg.
Funkcja jest lokalna, zatem poniższy kod nie skompiluje się:
public class Test
{
public void DoSomething()
{
int GetValue(int a)
{
return a;
}
Console.WriteLine(GetValue(5));
}
public void DoSomething1()
{
Console.WriteLine(GetValue(5));
}
}
Lokalną funkcję można tylko wywoływać w metodzie, w której ją zadeklarowano. Analogicznie poniższy kod wywoła błąd kompilacji:
class Program
{
private static void Main(string[] args)
{
Test test = new Test();
test.DoSomething();
test.GetValue(5);
}
}
Co prawda można wywołać test.DoSomething, ale test.GetValue już nie.
Możliwe jest również deklarowanie funkcji zagnieżdżonych wielokrotnie, tzn.:
public class Test
{
public void DoSomething()
{
int GetValue(int a)
{
string GetString(string b)
{
return b;
}
Console.WriteLine(GetString(a.ToString()+" as text"));
return a;
}
Console.WriteLine(GetValue(5));
}
}
Na ekranie najpierw wyświetli się “5 as text”, a potem 5. Z kolei następujący kod już nie skompiluje się:
public class Test
{
public void DoSomething()
{
int GetValue(int a)
{
string GetString(string b)
{
return b;
}
return a;
}
Console.WriteLine(GetString(a.ToString() + " as text"));
}
}
Oznacza to, że z funkcji A można jedynie odnosić się do tej najbardziej zewnętrznej funkcji lokalnej. Nic z kolei nie stoi na przeszkodzie, aby zadeklarować kilka metod o tym samym stopniu zagnieżdżenia:
public class Test
{
public void DoSomething()
{
int GetValue(int a)
{
return a;
}
string GetString(string b)
{
return b;
}
Console.WriteLine(GetString("5 as text"));
Console.WriteLine(GetValue(5));
}
}
Jakiekolwiek modyfikatory są zabronione, np. taki kod nie skompiluje się:
public class Test
{
public void DoSomething()
{
static int GetValue(int a)
{
return a;
}
Console.WriteLine(GetValue(5));
}
}
Analogicznie sprawa wygląda z public czy nawet private.
Możliwe jest z kolei z korzystanie z metod wyrażonych w formie lambda (nowość z c# 6.0):
public class Test
{
public void DoSomething()
{
int GetValue(int a) => a;
Console.WriteLine(GetValue(5));
}
}
Funkcja lokalna może mieć również dostęp do zmiennych zadeklarowanych w zewnętrznej funkcji, tzn.:
public class Test
{
public void DoSomething()
{
int outerValue = 43;
void Nested()
{
Console.WriteLine(outerValue);
outerValue = 100;
}
Nested();
Console.WriteLine(outerValue);
}
}
Na ekranie wyświetli się 43, a potem 100, ponieważ wartości kopiowane są w analogiczny sposób jak to ma miejsce w anonimowych funkcjach. Kolejne zagnieżdżenia mogą korzystać ze zmiennych zadeklarowanych w nadrzędnych funkcjach.
Na zakończenie warto spojrzeć co naprawdę jest wygenerowane w tle:
.class /*02000003*/ public auto ansi beforefieldinit
ConsoleApplication1.Test
extends [mscorlib]System.Object
{
.method /*06000003*/ public hidebysig instance void
DoSomething() cil managed
{
.maxstack 8
// [22 9 - 22 10]
IL_0000: nop
IL_0001: nop
// [27 13 - 27 44]
IL_0002: ldc.i4.5
IL_0003: call int32 ConsoleApplication1.Test::'<DoSomething>g__GetValue0_0'(int32)
IL_0008: call void [mscorlib]System.Console::WriteLine(int32)
IL_000d: nop
// [28 9 - 28 10]
IL_000e: ret
} // end of method Test::DoSomething
.method /*06000004*/ public hidebysig specialname rtspecialname instance void
.ctor() cil managed
{
.maxstack 8
IL_0000: ldarg.0 // this
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: ret
} // end of method Test::.ctor
.method /*06000005*/ assembly hidebysig static int32
'<DoSomething>g__GetValue0_0'(
/*08000002*/ int32 a
) cil managed
{
.custom /*0C00000F*/ instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() /*06003F5F*/
= (01 00 00 00 )
.maxstack 1
.locals init (
[0] int32 V_0
)
// [24 13 - 24 14]
IL_0000: nop
// [25 17 - 25 26]
IL_0001: ldarg.0 // a
IL_0002: stloc.0 // V_0
IL_0003: br.s IL_0005
// [26 13 - 26 14]
IL_0005: ldloc.0 // V_0
IL_0006: ret
} // end of method Test::'<DoSomething>g__GetValue0_0'
} // end of class ConsoleApplication1.Tes
Warto zwrócić uwagę na dwie sygnatury:
.method /*06000003*/ public hidebysig instance void
DoSomething() cil managed
[/csharp
oraz
.method /*06000005*/ assembly hidebysig static int32
'<DoSomething>g__GetValue0_0'(
/*08000002*/ int32 a
) cil managed
.
Innymi słowy, w praktyce dwie różne metody są generowane (lokalna funkcja to ta prywatna).