Udostępnij za pośrednictwem


Omówienie nowych funkcji w języku C# 6

Wersja 6 języka C# nadal rozwija język, aby mieć mniej standardowy kod, lepszą przejrzystość i większą spójność. Czystsza składnia inicjowania, możliwość używania funkcji await w blokach catch/finally oraz warunkowa wartość null? operator jest szczególnie przydatny.

Uwaga

Aby uzyskać informacje o najnowszej wersji języka C# — wersja 7 — zapoznaj się z artykułem What's New in C# 7.0 (Co nowego w języku C# 7.0)

W tym dokumencie przedstawiono nowe funkcje języka C# 6. Jest on w pełni obsługiwany przez kompilator mono, a deweloperzy mogą zacząć korzystać z nowych funkcji na wszystkich platformach docelowych platform Xamarin.

Co nowego w wideo w języku C# 6

Korzystanie z języka C# 6

Kompilator języka C# 6 jest używany we wszystkich ostatnich wersjach Visual Studio dla komputerów Mac. Osoby korzystające z kompilatorów wiersza polecenia powinny potwierdzić, że mcs --version zwraca wartość 4.0 lub nowszą. Visual Studio dla komputerów Mac użytkownicy mogą sprawdzić, czy mają zainstalowaną platformę Mono 4 (lub nowszą), odwołując się do Informacje o Visual Studio dla komputerów Mac Visual Studio dla komputerów Mac >> Pokaż szczegóły.

Mniej kociołowy

używanie statycznego

Wyliczenia i niektóre klasy, takie jak System.Math, są głównie posiadaczami wartości statycznych i funkcji. W języku C# 6 można zaimportować wszystkie statyczne elementy członkowskie typu za pomocą jednej using static instrukcji. Porównaj typową funkcję trygonometryczną w języku C# 5 i C# 6:

// Classic C#
class MyClass
{
    public static Tuple<double,double> SolarAngleOld(double latitude, double declination, double hourAngle)
    {
        var tmp = Math.Sin (latitude) * Math.Sin (declination) + Math.Cos (latitude) * Math.Cos (declination) * Math.Cos (hourAngle);
        return Tuple.Create (Math.Asin (tmp), Math.Acos (tmp));
    }
}

// C# 6
using static System.Math;

class MyClass
{
    public static Tuple<double, double> SolarAngleNew(double latitude, double declination, double hourAngle)
    {
        var tmp = Asin (latitude) * Sin (declination) + Cos (latitude) * Cos (declination) * Cos (hourAngle);
        return Tuple.Create (Asin (tmp), Acos (tmp));
    }
}

using static nie udostępnia pól publicznych const , takich jak Math.PI i Math.E, bezpośrednio:

for (var angle = 0.0; angle <= Math.PI * 2.0; angle += Math.PI / 8) ... 
//PI is const, not static, so requires Math.PI

używanie statycznych z metodami rozszerzeń

Obiekt using static działa nieco inaczej z metodami rozszerzenia. Mimo że metody rozszerzeń są pisane przy użyciu staticmetody , nie mają sensu bez wystąpienia, na którym można pracować. Dlatego gdy using static jest używany z typem, który definiuje metody rozszerzenia, metody rozszerzenia stają się dostępne na ich typie docelowym (typ metody this ). Na przykład using static System.Linq.Enumerable można użyć do rozszerzenia interfejsu IEnumerable<T> API obiektów bez wprowadzania wszystkich typów LINQ:

using static System.Linq.Enumerable;
using static System.String;

class Program
{
    static void Main()
    {
        var values = new int[] { 1, 2, 3, 4 };
        var evenValues = values.Where (i => i % 2 == 0);
        System.Console.WriteLine (Join(",", evenValues));
    }
}

W poprzednim przykładzie pokazano różnicę w zachowaniu: metoda Enumerable.Where rozszerzenia jest skojarzona z tablicą, podczas gdy metoda String.Join statyczna może być wywoływana bez odwołania do String typu.

nameof Expressions

Czasami chcesz odwołać się do nazwy nadanej zmiennej lub pola. W języku C# 6 nameof(someVariableOrFieldOrType) zostanie zwrócony ciąg "someVariableOrFieldOrType". Na przykład podczas zgłaszania elementu ArgumentException bardzo prawdopodobne jest nadanie nazwy, który argument jest nieprawidłowy:

throw new ArgumentException ("Problem with " + nameof(myInvalidArgument))

Główną zaletą nameof wyrażeń jest to, że są sprawdzane pod kątem typu i są zgodne z refaktoryzowaniem opartym na narzędziu. Sprawdzanie typów nameof wyrażeń jest szczególnie mile widziane w sytuacjach, w których element string jest używany do dynamicznego kojarzenia typów. Na przykład w systemie iOS element jest string używany do określania typu używanego do tworzenia prototypów UITableViewCell obiektów w obiekcie UITableView. nameof może zapewnić, że to skojarzenie nie kończy się niepowodzeniem z powodu błędu lub niechlujnej refaktoryzacji:

public override UITableViewCell GetCell (UITableView tableView, NSIndexPath indexPath)
{
    var cell = tableView.DequeueReusableCell (nameof(CellTypeA), indexPath);
    cell.TextLabel.Text = objects [indexPath.Row].ToString ();
    return cell;
}

Mimo że można przekazać kwalifikowaną nazwę do nameof, zwracany jest tylko ostatni element (po ostatnim .). Na przykład można dodać powiązanie danych w zestawie narzędzi Xamarin.Forms:

var myReactiveInstance = new ReactiveType ();
var myLabelOld.BindingContext = myReactiveInstance;
var myLabelNew.BindingContext = myReactiveInstance;
var myLabelOld.SetBinding (Label.TextProperty, "StringField");
var myLabelNew.SetBinding (Label.TextProperty, nameof(ReactiveType.StringField));

Dwa wywołania , które SetBinding przekazują identyczne wartości: nameof(ReactiveType.StringField) to "StringField", a nie "ReactiveType.StringField" tak, jak początkowo można się spodziewać.

Operator warunkowy o wartości null

Wcześniejsze aktualizacje języka C# wprowadziły koncepcje typów dopuszczających wartość null oraz operator ?? łączenia wartości null, aby zmniejszyć ilość kodu standardowego podczas obsługi wartości dopuszczających wartość null. Język C# 6 kontynuuje ten motyw za pomocą operatora ?.warunkowego o wartości null. Jeśli jest używany w obiekcie po prawej stronie wyrażenia, operator warunkowy o wartości null zwraca wartość składową, jeśli obiekt nie null jest i null w inny sposób:

var ss = new string[] { "Foo", null };
var length0 = ss [0]?.Length; // 3
var length1 = ss [1]?.Length; // null
var lengths = ss.Select (s => s?.Length ?? 0); //[3, 0]

(Zarówno, jak length0 i length1 są wnioskowane o typie int?)

Ostatni wiersz w poprzednim przykładzie pokazuje ? operator warunkowy o wartości null w połączeniu z operatorem ?? łączenia wartości null. Nowy operator warunkowy C# 6 o wartości null zwraca null drugi element w tablicy, w którym operator łączenia wartości null jest uruchamiany i dostarcza 0 do lengths tablicy (niezależnie od tego, czy jest to właściwe, czy nie, oczywiście, specyficzne dla problemu).

Operator warunkowy o wartości null powinien znacznie zmniejszyć ilość standardowego sprawdzania wartości null niezbędnej w wielu aplikacjach.

Istnieją pewne ograniczenia dotyczące operatora warunkowego o wartości null z powodu niejednoznaczności. Nie można od razu wykonać instrukcji ? z listą argumentów nawiasów, ponieważ możesz mieć nadzieję, że masz do czynienia z pełnomocnikiem:

SomeDelegate?("Some Argument") // Not allowed

Invoke Można jednak użyć metody do oddzielenia ? elementu od listy argumentów i nadal jest to wyraźna poprawa w stosunku do nullbloku kontrolnego płyty kotłowej:

public event EventHandler HandoffOccurred;
public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{
    HandoffOccurred?.Invoke (this, userActivity.UserInfo);
    return true;
}

Interpolacja ciągów

Funkcja String.Format tradycyjnie używa indeksów jako symboli zastępczych w ciągu formatu, np. String.Format("Expected: {0} Received: {1}.", expected, received). Oczywiście dodanie nowej wartości zawsze wiązało się z irytującym zadaniem zliczania argumentów, ponownego numerowania symboli zastępczych i wstawiania nowego argumentu w odpowiedniej sekwencji na liście argumentów.

Nowa funkcja interpolacji ciągów języka C# 6 znacznie poprawia wartość .String.Format Teraz możesz bezpośrednio nazwać zmienne w ciągu poprzedzonym prefiksem $. Przykład:

$"Expected: {expected} Received: {received}."

Zmienne są oczywiście sprawdzane, a błędna lub niedostępna zmienna spowoduje błąd kompilatora.

Symbole zastępcze nie muszą być prostymi zmiennymi, mogą to być dowolne wyrażenie. W tych symbolach zastępczych można używać cudzysłowów bez ucieczki od tych cudzysłowów. Zwróć na przykład uwagę na następujące kwestie "s" :

var s = $"Timestamp: {DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}"

Interpolacja ciągów obsługuje składnię String.Formatwyrównania i formatowania . Tak jak wcześniej napisałeś {index, alignment:format}, w języku C# 6 piszesz {placeholder, alignment:format}:

using static System.Linq.Enumerable;
using System;

class Program
{
    static void Main ()
    {
        var values = new int[] { 1, 2, 3, 4, 12, 123456 };
        foreach (var s in values.Select (i => $"The value is { i,10:N2}.")) {
            Console.WriteLine (s);
        }
    Console.WriteLine ($"Minimum is { values.Min(i => i):N2}.");
    }
}

wyniki w:

The value is       1.00.
The value is       2.00.
The value is       3.00.
The value is       4.00.
The value is      12.00.
The value is 123,456.00.
Minimum is 1.00.

Interpolacja ciągów jest cukrem składniowym dla String.Format: nie może być używana z @"" literałami ciągów i nie jest zgodna z parametrem const, nawet jeśli nie są używane symbole zastępcze:

const string s = $"Foo"; //Error : const requires value

W typowym przypadku użycia argumentów funkcji z interpolacją ciągów nadal trzeba uważać na ucieczkę, kodowanie i problemy z kulturą. Zapytania SQL i URL są oczywiście krytyczne do oczyszczania. Podobnie jak w przypadku String.Format, interpolacja ciągów używa elementu CultureInfo.CurrentCulture. Użycie CultureInfo.InvariantCulture jest nieco bardziej wordy:

Thread.CurrentThread.CurrentCulture  = new CultureInfo ("de");
Console.WriteLine ($"Today is: {DateTime.Now}"); //"21.05.2015 13:52:51"
Console.WriteLine ($"Today is: {DateTime.Now.ToString(CultureInfo.InvariantCulture)}"); //"05/21/2015 13:52:51"

Inicjowanie

Język C# 6 udostępnia szereg zwięzłych sposobów określania właściwości, pól i elementów członkowskich.

Inicjowanie automatycznej właściwości

Właściwości automatyczne można teraz zainicjować w taki sam zwięzły sposób, jak pola. Niezmienialne właściwości automatyczne można zapisywać tylko za pomocą polecenia getter:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;

W konstruktorze można ustawić wartość właściwości auto-tylko getter:

class ToDo
{
    public DateTime Due { get; set; } = DateTime.Now.AddDays(1);
    public DateTime Created { get; } = DateTime.Now;
    public string Description { get; }

    public ToDo (string description)
    {
        this.Description = description; //Can assign (only in constructor!)
    }

To inicjowanie właściwości automatycznych jest zarówno ogólną funkcją oszczędzania miejsca, jak i wartością logiczną dla deweloperów, którzy chcą podkreślić niezmienność w swoich obiektach.

Inicjatory indeksów

W języku C# 6 wprowadzono inicjatory indeksów, które umożliwiają ustawienie zarówno klucza, jak i wartości w typach, które mają indeksator. Zazwyczaj dotyczy Dictionaryto struktur danych stylu:

partial void ActivateHandoffClicked (WatchKit.WKInterfaceButton sender)
{
    var userInfo = new NSMutableDictionary {
        ["Created"] = NSDate.Now,
        ["Due"] = NSDate.Now.AddSeconds(60 * 60 * 24),
        ["Task"] = Description
    };
    UpdateUserActivity ("com.xamarin.ToDo.edit", userInfo, null);
    statusLabel.SetText ("Check phone");
}

Składowe funkcji wyrażeń

Funkcje lambda mają kilka korzyści, z których jeden po prostu oszczędza miejsce. Podobnie składowe klasy wyrażeń umożliwiają nieco bardziej zwięzłe wyrażanie małych funkcji niż było to możliwe w poprzednich wersjach języka C# 6.

Składowe funkcji wyrażeń używają składni strzałki lambda, a nie tradycyjnej składni bloku:

public override string ToString () => $"{FirstName} {LastName}";

Zwróć uwagę, że składnia lambda-arrow nie używa jawnego returnelementu . W przypadku funkcji zwracających voidwyrażenie musi być również instrukcją:

public void Log(string message) => System.Console.WriteLine($"{DateTime.Now.ToString ("s", System.Globalization.CultureInfo.InvariantCulture )}: {message}");

Elementy członkowskie wyrażeń nadal podlegają regule obsługiwanej dla metod, async ale nie właściwości:

//A method, so async is valid
public async Task DelayInSeconds(int seconds) => await Task.Delay(seconds * 1000);
//The following property will not compile
public async Task<int> LeisureHours => await Task.FromResult<char> (DateTime.Now.DayOfWeek.ToString().First()) == 'S' ? 16 : 5;

Wyjątki

Nie ma dwóch sposobów na to: obsługa wyjątków jest trudna do uzyskania właściwego rozwiązania. Nowe funkcje w języku C# 6 sprawiają, że obsługa wyjątków jest bardziej elastyczna i spójna.

Filtry wyjątków

Z definicji wyjątki występują w nietypowych okolicznościach i może być bardzo trudne do uzasadnienia i kodu na temat wszystkich sposobów wystąpienia wyjątku określonego typu. Język C# 6 wprowadza możliwość ochrony programu obsługi wykonywania za pomocą filtru ocenianego przez środowisko uruchomieniowe. Odbywa się to przez dodanie when (bool) wzorca po deklaracji normalnej catch(ExceptionType) . W poniższym filtrze rozróżnia błąd analizy odnoszący się do parametru date , w przeciwieństwie do innych błędów analizowania.

public void ExceptionFilters(string aFloat, string date, string anInt)
{
    try
    {
        var f = Double.Parse(aFloat);
        var d = DateTime.Parse(date);
        var n = Int32.Parse(anInt);
    } catch (FormatException e) when (e.Message.IndexOf("DateTime") > -1) {
        Console.WriteLine ($"Problem parsing \"{nameof(date)}\" argument");
    } catch (FormatException x) {
        Console.WriteLine ("Problem parsing some other argument");
    }
}

czekaj w połowu... Wreszcie...

Możliwości async wprowadzone w języku C# 5 były game-changer dla języka. W języku C# 5 await nie było dozwolone w catch blokach i finally i i irytuje, biorąc pod uwagę wartość async/await możliwości. Język C# 6 usuwa to ograniczenie, umożliwiając spójne oczekiwanie na wyniki asynchroniczne za pośrednictwem programu, jak pokazano w poniższym fragmencie kodu:

async void SomeMethod()
{
    try {
        //...etc...
    } catch (Exception x) {
        var diagnosticData = await GenerateDiagnosticsAsync (x);
        Logger.log (diagnosticData);
    } finally {
        await someObject.FinalizeAsync ();
    }
}

Podsumowanie

Język C# nadal ewoluuje, aby deweloperzy bardziej produktywni, jednocześnie promując dobre praktyki i pomocnicze narzędzia. W tym dokumencie omówiono nowe funkcje języka w języku C# 6 i krótko pokazano, jak są one używane.