C# 6.0–primary constructors

Update [16.10.2014]: Niestety opisana konstrukcja prawdopodobnie nie znajdzie się w finalnej wersji C# 6.0. Możliwe, że wcześniej czy później zostanie to zaimplementowane, ale wiadomo, różnie z tym bywa.

Czas zacząć pisać o nowym c#.  Najpierw pobierzmy wersję CTP z:

http://www.visualstudio.com/en-us/downloads/visual-studio-14-ctp-vs.aspx

Oczywiście to preview, więc odradzam instalowanie tego w pracy, chyba, że na VM. Lepiej nie ryzykować re-instalacją wszystkiego od nowa.

Często w kodzie można spotkać następujący wzorzec:

class Person
{
  private readonly string _firstName;
  private readonly string _lastName;

  public Person(string firstName,string lastName)
  {
      _firstName = firstName;
      _lastName = lastName;
  }
}

Kod często praktykowany, ale trochę nadmiarowy. Musimy zdefiniować konstruktor, tylko po to aby zainicjalizować kilka zmiennych. W C# do dyspozycji będziemy mieli następującą konstrukcję:

class Person(string firstName,string lastName)
{
  private readonly string _firstName=firstName;
  private readonly string _lastName = lastName;

  public override string ToString()
  {
      return string.Format("{0} {1}", _firstName, _lastName);
  }
}

Konstrukcja przydatna, aczkolwiek trochę dziwna. Nic takiego nie mieliśmy w CPP czy w Javie. Myślę jednak, że można przyzwyczaić się i jest to po prostu praktyczne.

Jest to najzwyklejszy konstruktor i Person zainicjalizujemy następująco:

Person person = new Person("piotr","zielinski");
Console.WriteLine(person.ToString());

Jeśli jednak będziemy chcieli skompilować to, dostaniemy błąd:

Error    1    Feature 'primary constructor' is only available in 'experimental' language version.    c:\ConsoleApplication2\ConsoleApplication2\Program.cs    17    21    ConsoleApplication2

Nic dziwnego. To tylko preview, nie ma pewności nawet czy ta konstrukcja będzie w oficjalnej wersji. Jeśli jednak chcemy to odpalić, przejdźmy do edycji pliku projektu i dodajmy tag LangVersion z ustawioną wartością “Experimental”:

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <PlatformTarget>AnyCPU</PlatformTarget>
    <DebugSymbols>true</DebugSymbols>
    <DebugType>full</DebugType>
    <Optimize>false</Optimize>
    <OutputPath>bin\Debug\</OutputPath>
    <DefineConstants>DEBUG;TRACE</DefineConstants>
    <ErrorReport>prompt</ErrorReport>
    <WarningLevel>4</WarningLevel>
    <LangVersion>Experimental</LangVersion>
  </PropertyGroup>

Próba użycia konstruktora bezparametrowego zakończy się również błędem:

Person person = new Person();

Tak jak wcześniej, zdefiniowanie niestandardowego konstruktora powoduje, że domyślny nie jest już automatycznie generowany.

Nic nie stoi na przeszkodzie, abyśmy pomieszali konstruktory:

class Person(string firstName,string lastName)
{
  private readonly string _firstName=firstName;
  private readonly string _lastName = lastName;
  private int _age;

  public Person(string firstName,string lastName,int age) :this(firstName,lastName)
  {
      _age = age;
  }
}

Oczywiście możemy  zdefiniować wyłącznie jeden primary constructor.

Zajrzyjmy również do IL:

.class nested private auto ansi beforefieldinit Person
    extends [mscorlib]System.Object
{
    // Fields
    .field private initonly string _firstName
    .field private initonly string _lastName
    .field private int32 _age

    // Methods
    .method public hidebysig specialname rtspecialname 
        instance void .ctor (
            string firstName,
            string lastName
        ) cil managed 
    {
        // Method begins at RVA 0x2067
        // Code size 22 (0x16)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldarg.1
        IL_0002: stfld string ConsoleApplication2.Program/Person::_firstName
        IL_0007: ldarg.0
        IL_0008: ldarg.2
        IL_0009: stfld string ConsoleApplication2.Program/Person::_lastName
        IL_000e: ldarg.0
        IL_000f: call instance void [mscorlib]System.Object::.ctor()
        IL_0014: nop
        IL_0015: ret
    } // end of method Person::.ctor

    .method public hidebysig specialname rtspecialname 
        instance void .ctor (
            string firstName,
            string lastName,
            int32 age
        ) cil managed 
    {
        // Method begins at RVA 0x207e
        // Code size 18 (0x12)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: ldarg.1
        IL_0002: ldarg.2
        IL_0003: call instance void ConsoleApplication2.Program/Person::.ctor(string, string)
        IL_0008: nop
        IL_0009: nop
        IL_000a: ldarg.0
        IL_000b: ldarg.3
        IL_000c: stfld int32 ConsoleApplication2.Program/Person::_age
        IL_0011: ret
    } // end of method Person::.ctor

} // end of class Person

Tutaj nic nie zmieniło się. Konstruktor został po prostu wygenerowany w klasyczny sposób.

4 thoughts on “C# 6.0–primary constructors”

  1. Mysle ze warto wspomniec, ze ta funkcja bedzie usunieta z c# 6.0 w zwiazku z innymi problemami z przeciagajacym sie Roslynem:

    https://roslyn.codeplex.com/discussions/568820

    The features we are cutting are:
    * Primary constructors in C# (along with initializers in structs)
    * Declaration expressions in C# / Out parameters in VB

Leave a Reply

Your email address will not be published.