Поиск…


Вступление

Эта шестнадцатая итерация языка C # предоставляется компилятором Roslyn. Этот компилятор вышел с версией 4.6 .NET Framework, однако он может генерировать код с обратной совместимостью, чтобы разрешить таргетинг на ранние версии рамок. Код C # версии 6 может быть скомпилирован полностью совместимым образом с .NET 4.0. Его также можно использовать для ранних фреймворков, однако некоторые функции, требующие дополнительной поддержки фреймворка, могут работать некорректно.

замечания

Шестая версия C # была выпущена в июле 2015 года вместе с Visual Studio 2015 и .NET 4.6.

Помимо добавления некоторых новых функций языка, он включает полную переработку компилятора. Ранее csc.exe было родным приложением Win32, написанным на C ++, а C # 6 теперь является управляемым .NET-программным обеспечением, написанным на C #. Этот переписчик был известен как проект «Roslyn», и код теперь открыт с открытым исходным кодом и доступен на GitHub .

Имя оператора

Оператор nameof возвращает имя элемента кода в виде string . Это полезно при бросании исключений, связанных с аргументами метода, а также при внедрении INotifyPropertyChanged .

public string SayHello(string greeted)
{
    if (greeted == null)
        throw new ArgumentNullException(nameof(greeted));
    
    Console.WriteLine("Hello, " + greeted);
}

Оператор nameof вычисляется во время компиляции и изменяет выражение в строковый литерал. Это также полезно для строк, названных в честь их члена, который их раскрывает. Рассмотрим следующее:

public static class Strings
{
    public const string Foo = nameof(Foo); // Rather than Foo = "Foo"
    public const string Bar = nameof(Bar); // Rather than Bar = "Bar"
}

Поскольку выражения nameof являются константами времени компиляции, их можно использовать в атрибутах, метках case , операторах switch и т. Д.


Удобно использовать nameof с Enum s. Вместо:

Console.WriteLine(Enum.One.ToString());

можно использовать:

Console.WriteLine(nameof(Enum.One))

В обоих случаях выход будет One .


Оператор nameof может обращаться к nameof элементам, используя синтаксис типа static. Вместо того, чтобы делать:

string foo = "Foo";
string lengthName = nameof(foo.Length);

Может быть заменено на:

string lengthName = nameof(string.Length);

В обоих примерах выход будет Length . Однако последний препятствует созданию ненужных экземпляров.


Хотя nameof оператора работает с большинством языковых конструкций, существуют некоторые ограничения. Например, вы не можете использовать оператор nameof для открытых общих типов или возвращаемых значений метода:

public static int Main()
{   
    Console.WriteLine(nameof(List<>)); // Compile-time error
    Console.WriteLine(nameof(Main())); // Compile-time error
}

Кроме того, если вы примените его к родовому типу, параметр родового типа будет проигнорирован:

Console.WriteLine(nameof(List<int>));  // "List"
Console.WriteLine(nameof(List<bool>)); // "List"

Дополнительные примеры см. В этом разделе, посвященном nameof .


Обходной путь для предыдущих версий ( подробнее )

Хотя оператор nameof не существует в C # для версий до 6.0, аналогичную функциональность можно использовать с помощью MemberExpression как MemberExpression ниже:

6,0

Выражение:

public static string NameOf<T>(Expression<Func<T>> propExp)
{
    var memberExpression = propExp.Body as MemberExpression;
    return memberExpression != null ? memberExpression.Member.Name : null;
}

public static string NameOf<TObj, T>(Expression<Func<TObj, T>> propExp)
{
    var memberExpression = propExp.Body as MemberExpression;
    return memberExpression != null ? memberExpression.Member.Name : null;
}

Использование:

string variableName = NameOf(() => variable);
string propertyName = NameOf((Foo o) => o.Bar);

Обратите внимание, что этот подход заставляет nameof дерево выражений при каждом вызове, поэтому производительность намного хуже по сравнению с nameof оператора, который оценивается во время компиляции и имеет нулевые служебные данные во время выполнения.

Элементы функции выражения

Элементы функции, выражающие выражение, позволяют использовать лямбда-выражения в качестве элементов-членов. Для простых членов это может привести к более чистым и понятным кодам.

Функциональные выражения могут использоваться для свойств, индексаторов, методов и операторов.


свойства

public decimal TotalPrice => BasePrice + Taxes;

Эквивалентно:

public decimal TotalPrice
{
    get
    {
        return BasePrice + Taxes;
    }
}

Когда функция expression-bodied используется с свойством, свойство реализуется как свойство только для getter.

Посмотреть демо


индексаторы

public object this[string key] => dictionary[key];

Эквивалентно:

public object this[string key]
{
    get
    {
        return dictionary[key];
    }
}

методы

static int Multiply(int a, int b) => a * b;

Эквивалентно:

static int Multiply(int a, int b)
{
    return a * b;
}

Который также может использоваться с void методами:

public void Dispose() => resource?.Dispose();

В класс Pair<T> можно добавить переопределение ToString :

public override string ToString() => $"{First}, {Second}";

Кроме того, этот упрощенный подход работает с ключевым словом override :

public class Foo
{
    public int Bar { get; }

    public string override ToString() => $"Bar: {Bar}";
}

операторы

Это также может использоваться операторами:

public class Land
{
    public double Area { get; set; }

    public static Land operator +(Land first, Land second) =>
        new Land { Area = first.Area + second.Area };
}

Ограничения

Элементы функции с выражением имеют некоторые ограничения. Они не могут содержать операторы блоков и любые другие операторы, которые содержат блоки: if , switch , for , foreach , while , do , try и т. Д.

Некоторые операторы if могут быть заменены на тернарные операторы. Некоторые операторы for и foreach могут быть преобразованы в запросы LINQ, например:

IEnumerable<string> Digits
{
    get
    {
        for (int i = 0; i < 10; i++)
            yield return i.ToString();
    }
}
IEnumerable<string> Digits => Enumerable.Range(0, 10).Select(i => i.ToString());

Во всех остальных случаях можно использовать старый синтаксис для членов функции.

Элементы функции Expression-body могут содержать async / await , но это часто избыточно:

async Task<int> Foo() => await Bar();  

Может быть заменено на:

Task<int> Foo() => Bar();

Исключительные фильтры

Фильтры исключений дают разработчикам возможность добавить условие (в виде boolean выражения) в блок catch , позволяя catch выполнять, только если условие оценивается как true .

Фильтры исключений позволяют распространять информацию об отладке в исходном исключении, где при использовании оператора if внутри блока catch и повторного выброса исключения останавливает распространение отладочной информации в исходном исключении. При использовании фильтров исключений исключение продолжает распространяться вверх в стеке вызовов, если условие не выполняется. В результате фильтры исключений значительно облегчают процесс отладки. Вместо того, чтобы останавливаться на инструкции throw , отладчик остановится на выражении, исключающем исключение, с сохранением текущего состояния и всех локальных переменных. Аналогичным образом затрагиваются аварийные свалки.

Фильтры исключений поддерживались CLR с самого начала, и они были доступны из VB.NET и F # в течение более десяти лет, подвергая часть модели обработки исключений CLR. Только после выпуска C # 6.0 функциональность также была доступна для разработчиков C #.


Использование фильтров исключений

Фильтры исключений используются путем добавления предложения when в выражение catch . Можно использовать любое выражение, возвращающее bool в предложении when (кроме ожидания ). Объявленная переменная exception ex доступна из предложения when :

var SqlErrorToIgnore = 123;
try
{
    DoSQLOperations();
}
catch (SqlException ex) when (ex.Number != SqlErrorToIgnore)
{
    throw new Exception("An error occurred accessing the database", ex);
}

Множество блоков catch when предложения могут быть объединены. Первое, when предложение, возвращающее true приведет к тому, что исключение будет обнаружено. Его блок catch будет введен, в то время как другие предложения catch будут проигнорированы (их, when предложения не будут оцениваться). Например:

try
{ ... }
catch (Exception ex) when (someCondition) //If someCondition evaluates to true,
                                          //the rest of the catches are ignored.
{ ... }
catch (NotImplementedException ex) when (someMethod()) //someMethod() will only run if
                                                       //someCondition evaluates to false
{ ... }
catch(Exception ex) // If both when clauses evaluate to false
{ ... }

Опасно, когда статья

предосторожность

Может быть опасно использовать фильтры исключений: когда Exception выбрано из предложения when , Exception из предложения when игнорируется и считается false . Такой подход позволяет разработчикам писать, when клаузулы, не заботясь о недопустимых случаях.

Следующий пример иллюстрирует такой сценарий:

public static void Main()
{
    int a = 7;
    int b = 0;
    try
    {
        DoSomethingThatMightFail();
    }
    catch (Exception ex) when (a / b == 0)
    {
        // This block is never reached because a / b throws an ignored
        // DivideByZeroException which is treated as false.
    }
    catch (Exception ex)
    {
        // This block is reached since the DivideByZeroException in the 
        // previous when clause is ignored.
    }
}

public static void DoSomethingThatMightFail()
{
    // This will always throw an ArgumentNullException.
    Type.GetType(null);
}

Посмотреть демо

Обратите внимание, что фильтры исключений исключают проблемы с запутанными номерами строк, связанные с использованием throw когда код ошибки находится в одной и той же функции. Например, в этом случае номер строки отображается как 6 вместо 3:

1. int a = 0, b = 0;
2. try {
3.     int c = a / b;
4. }
5. catch (DivideByZeroException) {
6.     throw;
7. }

Номер строки исключений сообщается как 6, потому что ошибка была поймана и повторно выбрана с помощью оператора throw в строке 6.

То же самое происходит с фильтрами исключений:

1. int a = 0, b = 0;
2. try {
3.     int c = a / b;
4. }
5. catch (DivideByZeroException) when (a != 0) {
6.     throw;
7. }

В этом примере a равно 0, тогда предложение catch игнорируется, но 3 указывается как номер строки. Это происходит потому, что они не разматывают стек . Более конкретно, исключение не попадает в строку 5, потому a на самом деле оно равно 0 и, следовательно, нет возможности исключить исключение в строке 6, потому что строка 6 не выполняется.


Регистрация в качестве побочного эффекта

Вызовы метода в состоянии могут вызывать побочные эффекты, поэтому фильтры исключения могут использоваться для запуска кода на исключениях, не вылавливая их. Общим примером, который использует это, является метод Log который всегда возвращает false . Это позволяет отслеживать информацию журнала во время отладки без необходимости повторного выброса исключения.

Имейте в виду, что, хотя это, кажется, удобный способ ведения журнала, это может быть рискованным, особенно если используются сторонние протоколирующие сборки. Они могут генерировать исключения при входе в не очевидные ситуации, которые могут быть легко обнаружены (см. Рискованное, when(...) выше).

try
{
    DoSomethingThatMightFail(s);
}
catch (Exception ex) when (Log(ex, "An error occurred"))
{
    // This catch block will never be reached
}

// ...

static bool Log(Exception ex, string message, params object[] args)
{
    Debug.Print(message, args);
    return false;
}

Посмотреть демо

Общий подход в предыдущих версиях C # заключался в регистрации и повторном выбросе исключения.

6,0
try
{
    DoSomethingThatMightFail(s);
}
catch (Exception ex)
{
     Log(ex, "An error occurred");
     throw;
}

// ...

static void Log(Exception ex, string message, params object[] args)
{
    Debug.Print(message, args);
}

Посмотреть демо


finally блок

Блок finally выполняется каждый раз, независимо от того, выбрано ли исключение или нет. Одна тонкость с выражениями, when есть фильтры исключений, выполняется дальше в стеке, прежде чем вводить внутренние блоки finally . Это может привести к неожиданным результатам и поведению, когда код пытается изменить глобальное состояние (например, пользователь или культура текущего потока) и установить его обратно в блок finally .

Пример: finally блок

private static bool Flag = false;

static void Main(string[] args)
{
    Console.WriteLine("Start");
    try
    {
        SomeOperation();
    }
    catch (Exception) when (EvaluatesTo())
    {
        Console.WriteLine("Catch");
    }
    finally
    {
        Console.WriteLine("Outer Finally");
    }
}

private static bool EvaluatesTo()
{
    Console.WriteLine($"EvaluatesTo: {Flag}");
    return true;
}

private static void SomeOperation()
{
    try
    {
        Flag = true;
        throw new Exception("Boom");
    }
    finally
    {
        Flag = false;
        Console.WriteLine("Inner Finally");
    }
}

Производительность:

Начните
EvaluatesTo: True
Внутренний Наконец
Ловить
Внешнее окончание

Посмотреть демо

В приведенном выше примере, если метод SomeOperation не желает «утечки», глобальное состояние изменяется на предложения вызывающего, when оно должно быть, оно также должно содержать блок catch для изменения состояния. Например:

private static void SomeOperation()
{
    try
    {
        Flag = true;
        throw new Exception("Boom");
    }
    catch
    {
       Flag = false;
       throw;
    }
    finally
    {
        Flag = false;
        Console.WriteLine("Inner Finally");
    }
}

Также часто бывает, что IDisposable вспомогательные классы используют семантику использования блоков для достижения той же цели, что и IDisposable.Dispose всегда IDisposable.Dispose до того, как исключение, вызванное внутри using блока, начнет разворачивать стек.

Инициализаторы автоистории

Вступление

Свойства можно инициализировать с помощью оператора = после закрытия } . В приведенном ниже разделе « Coordinate » показаны доступные параметры инициализации свойства:

6,0
public class Coordinate
{ 
    public int X { get; set; } = 34; // get or set auto-property with initializer

    public int Y { get; } = 89;      // read-only auto-property with initializer              
}

Аксессоры с различной видимостью

Вы можете инициализировать автоматические свойства, которые имеют различную видимость для их аксессуаров. Вот пример с защищенным сеттером:

    public string Name { get; protected set; } = "Cheeze";

Аксессор также может быть internal , internal protected или private .


Свойства только для чтения

Помимо гибкости с видимостью, вы также можете инициализировать автоматические свойства только для чтения. Вот пример:

    public List<string> Ingredients { get; } = 
        new List<string> { "dough", "sauce", "cheese" };

В этом примере также показано, как инициализировать свойство сложным типом. Кроме того, авто-свойства не могут быть только для записи, поэтому также исключает инициализацию только для записи.


Старый стиль (pre C # 6.0)

До C # 6 это требовало гораздо более подробного кода. Мы использовали одну дополнительную переменную, называемую свойство backing для свойства, чтобы присвоить значение по умолчанию или инициализировать публичное свойство, как показано ниже,

6,0
public class Coordinate
{
    private int _x = 34;
    public int X { get { return _x; } set { _x = value; } }

    private readonly int _y = 89;
    public int Y { get { return _y; } }
    
    private readonly int _z;
    public int Z { get { return _z; } }

    public Coordinate()
    {
        _z = 42;
    }
}

Примечание. До C # 6.0 вы все равно можете инициализировать чтение и запись автоматически реализованных свойств (свойств с помощью getter и setter) внутри конструктора, но вы не можете инициализировать свойство inline с его объявлением

Посмотреть демо


использование

Инициализаторы должны оценивать статические выражения, как и инициализаторы полей. Если вам нужно ссылаться на нестатические элементы, вы можете либо инициализировать свойства в конструкторах, как раньше, либо использовать свойства с выражением. Нестатические выражения, подобные приведенным ниже (закомментированные), генерируют ошибку компилятора:

// public decimal X { get; set; } = InitMe();  // generates compiler error

decimal InitMe() { return 4m; }

Но статические методы могут использоваться для инициализации автоматических свойств:

public class Rectangle
{
    public double Length { get; set; } = 1;
    public double Width { get; set; } = 1;
    public double Area { get; set; } = CalculateArea(1, 1);

    public static double CalculateArea(double length, double width)
    {
        return length * width;
    }
}

Этот метод также можно применять к свойствам с различным уровнем доступа:

public short Type { get; private set; } = 15;

Инициализатор автоматического свойства позволяет присваивать свойства непосредственно в объявлении. Для свойств только для чтения он выполняет все требования, необходимые для обеспечения неизменности свойства. Рассмотрим, например, класс FingerPrint в следующем примере:

public class FingerPrint
{
  public DateTime TimeStamp { get; } = DateTime.UtcNow;

  public string User { get; } =
    System.Security.Principal.WindowsPrincipal.Current.Identity.Name;

  public string Process { get; } =
    System.Diagnostics.Process.GetCurrentProcess().ProcessName;
}

Посмотреть демо


Предупреждающие примечания

Постарайтесь не путать инициализаторы авто-свойств или поля с похожими способами выражения-тела, которые используют => в противоположность = , и поля, которые не включают { get; } .

Например, каждая из следующих деклараций различна.

public class UserGroupDto
{
    // Read-only auto-property with initializer:       
    public ICollection<UserDto> Users1 { get; } = new HashSet<UserDto>();
    
    // Read-write field with initializer:
    public ICollection<UserDto> Users2 = new HashSet<UserDto>();

    // Read-only auto-property with expression body:
    public ICollection<UserDto> Users3 => new HashSet<UserDto>();
}

Отсутствует { get; } в объявлении свойства приводит к публичному полю. И автообновление Users1 чтения, и поле чтения-записи Users2 инициализируются только один раз, но общедоступное поле позволяет изменять экземпляр коллекции вне класса, что обычно нежелательно. Изменение автоматического свойства только для чтения с телом выражения в свойство только для чтения с инициализатором требует не только удаления > from => , но и добавления { get; } .

Users3 символ ( => вместо = ) в Users3 приводит к каждому доступу к свойству, возвращающему новый экземпляр HashSet<UserDto> который, хотя действительный C # (с точки зрения компилятора) вряд ли будет желательным, когда используется для члена коллекции.

Вышеприведенный код эквивалентен:

public class UserGroupDto
{
    // This is a property returning the same instance
    // which was created when the UserGroupDto was instantiated.
    private ICollection<UserDto> _users1 = new HashSet<UserDto>();
    public ICollection<UserDto> Users1 { get { return _users1; } }

    // This is a field returning the same instance
    // which was created when the UserGroupDto was instantiated.
    public virtual ICollection<UserDto> Users2 = new HashSet<UserDto>();

    // This is a property which returns a new HashSet<UserDto> as
    // an ICollection<UserDto> on each call to it.
    public ICollection<UserDto> Users3 { get { return new HashSet<UserDto>(); } }
}

Инициализаторы индекса

Инициализаторы индексов позволяют одновременно создавать и инициализировать объекты с индексами.

Это упрощает инициализацию словарей:

var dict = new Dictionary<string, int>()
{
    ["foo"] = 34,
    ["bar"] = 42
};

Любой объект, который имеет индексированный getter или setter, может использоваться с этим синтаксисом:

class Program
{
    public class MyClassWithIndexer
    {
        public int this[string index]
        {
            set
            {
                Console.WriteLine($"Index: {index}, value: {value}");
            }
        }
    }

    public static void Main()
    {
        var x = new MyClassWithIndexer()
        {
            ["foo"] = 34,
            ["bar"] = 42
        };

        Console.ReadKey();
    }
}

Выход:

Индекс: foo, значение: 34
Индекс: bar, значение: 42

Посмотреть демо

Если класс имеет несколько индексаторов, их можно назначить в одной группе операторов:

class Program
{
    public class MyClassWithIndexer
    {
        public int this[string index]
        {
            set
            {
                Console.WriteLine($"Index: {index}, value: {value}");
            }
        }
        public string this[int index]
        {
            set
            {
                Console.WriteLine($"Index: {index}, value: {value}");
            }
        }
    }

    public static void Main()
    {
        var x = new MyClassWithIndexer()
        {
            ["foo"] = 34,
            ["bar"] = 42,
            [10] = "Ten",
            [42] = "Meaning of life"
        };
    }
}

Выход:

Индекс: foo, значение: 34
Индекс: bar, значение: 42
Индекс: 10, значение: Десять
Индекс: 42, значение: Значение жизни

Следует отметить, что аксессор set индексов может вести себя по-разному по сравнению с методом Add (используется в инициализаторах коллекции).

Например:

var d = new Dictionary<string, int>
{
    ["foo"] = 34,
    ["foo"] = 42,
}; // does not throw, second value overwrites the first one

против:

var d = new Dictionary<string, int>
{
    { "foo", 34 },
    { "foo", 42 },
}; // run-time ArgumentException: An item with the same key has already been added.

Строчная интерполяция

Строковая интерполяция позволяет разработчику комбинировать variables и текст для формирования строки.


Основной пример

int две переменные int : foo и bar .

int foo = 34;
int bar = 42;

string resultString = $"The foo is {foo}, and the bar is {bar}.";

Console.WriteLine(resultString);

Выход :

Foo - 34, а бара - 42.

Посмотреть демо

Скобки внутри строк все еще можно использовать, например:

var foo = 34;
var bar = 42;

// String interpolation notation (new style)
Console.WriteLine($"The foo is {{foo}}, and the bar is {{bar}}.");

Это дает следующий результат:

Foo - {foo}, а bar - {bar}.


Использование интерполяции со стенографическими строками verbatim

Использование @ перед строкой приведет к тому, что строка будет интерпретирована дословно. Таким образом, например, символы Unicode или разрывы строк будут оставаться такими же, как они были напечатаны. Однако это не повлияет на выражения в интерполированной строке, как показано в следующем примере:

Console.WriteLine($@"In case it wasn't clear:
\u00B9
The foo
is {foo},
and the bar
is {bar}.");
Выход:

В случае непонятности:
\ u00B9
Foo
составляет 34,
и бар
составляет 42.

Посмотреть демо


Выражения

При строковой интерполяции выражения в фигурных скобках {} также могут быть оценены. Результат будет вставлен в соответствующее место в строке. Например, чтобы вычислить максимум foo и bar и вставить его, используйте Math.Max в фигурных скобках:

Console.WriteLine($"And the greater one is: { Math.Max(foo, bar) }");

Выход:

И тем больше: 42

Примечание. Любые ведущие или конечные пробелы (включая пробел, вкладку и CRLF / новую строку) между фигурной скобкой и выражением полностью игнорируются и не включаются в вывод

Посмотреть демо

В качестве другого примера переменные могут быть отформатированы как валюта:

Console.WriteLine($"Foo formatted as a currency to 4 decimal places: {foo:c4}");

Выход:

Foo отформатирован как валюта до 4 знаков после запятой: $ 34,0000

Посмотреть демо

Или они могут быть отформатированы как даты:

Console.WriteLine($"Today is: {DateTime.Today:dddd, MMMM dd - yyyy}");

Выход:

Сегодня: понедельник, 20 июля - 2015 г.

Посмотреть демо

Операции с условным (тройным) оператором также могут быть оценены внутри интерполяции. Однако они должны быть заключены в круглые скобки, поскольку двоеточие в противном случае используется для указания форматирования, как показано выше:

Console.WriteLine($"{(foo > bar ? "Foo is larger than bar!" : "Bar is larger than foo!")}");

Выход:

Бар больше, чем foo!

Посмотреть демо

Условные выражения и спецификаторы формата могут быть смешаны:

Console.WriteLine($"Environment: {(Environment.Is64BitProcess ? 64 : 32):00'-bit'} process");

Выход:

Окружающая среда: 32-битный процесс


Эквивалентные последовательности

Символы обратной косой черты ( \ ) и quote ( " ) работают точно так же в интерполированных строках, как и в неинтерполированных строках, как для дословных, так и для невербальных строковых литералов:

Console.WriteLine($"Foo is: {foo}. In a non-verbatim string, we need to escape \" and \\ with backslashes.");
Console.WriteLine($@"Foo is: {foo}. In a verbatim string, we need to escape "" with an extra quote, but we don't need to escape \");

Выход:

Foo - 34. В строке, отличной от слов, нам нужно избежать «и \ с обратными косыми чертами.
Foo - 34. В стенографической строке нам нужно сбежать с дополнительной цитатой, но нам не нужно бежать \

Чтобы включить фигурные скобки { или } в интерполированной строке, используйте две фигурные скобки {{ или }} :

$"{{foo}} is: {foo}"

Выход:

{foo}: 34

Посмотреть демо


Тип FormattableString

Тип выражения интерполяции строки $"..." не всегда является простой строкой. Компилятор решает, какой тип назначить в зависимости от контекста:

string s = $"hello, {name}";
System.FormattableString s = $"Hello, {name}";
System.IFormattable s = $"Hello, {name}";

Это также порядок предпочтений типа, когда компилятору необходимо выбрать, какой перегруженный метод будет вызываться.

Новый тип , System.FormattableString , представляет собой строку составного формата вместе с аргументами, которые нужно отформатировать. Используйте это, чтобы писать приложения, которые обрабатывают аргументы интерполяции:

public void AddLogItem(FormattableString formattableString)
{
    foreach (var arg in formattableString.GetArguments())
    {
        // do something to interpolation argument 'arg'
    }

    // use the standard interpolation and the current culture info
    // to get an ordinary String:
    var formatted = formattableString.ToString();

    // ...
}

Вызовите вышеуказанный метод с помощью:

AddLogItem($"The foo is {foo}, and the bar is {bar}.");
Например, можно было бы отказаться от затрат на производительность форматирования строки, если уровень ведения журнала уже собирался отфильтровать элемент журнала.

Неявные преобразования

Имеются неявные преобразования типов из интерполированной строки:

var s = $"Foo: {foo}";
System.IFormattable s = $"Foo: {foo}";
Вы также можете создать переменную IFormattable которая позволяет вам преобразовать строку с инвариантным контекстом:
var s = $"Bar: {bar}";
System.FormattableString s = $"Bar: {bar}";

Методы текущей и инвариантной культуры

Если анализ кода включен, интерполированные строки будут выдавать предупреждение CA1305 (Укажите IFormatProvider ). Статический метод может использоваться для применения текущей культуры.

public static class Culture
{
    public static string Current(FormattableString formattableString)
    {
        return formattableString?.ToString(CultureInfo.CurrentCulture);
    }
    public static string Invariant(FormattableString formattableString)
    {
        return formattableString?.ToString(CultureInfo.InvariantCulture);
    }
}

Затем, чтобы создать правильную строку для текущей культуры, просто используйте выражение:

Culture.Current($"interpolated {typeof(string).Name} string.")
Culture.Invariant($"interpolated {typeof(string).Name} string.")
Примечание : Current и Invariant не могут быть созданы как методы расширения, потому что по умолчанию компилятор присваивает тип String интерполированному строковому выражению, из-за которого невозможно скомпилировать следующий код:

$"interpolated {typeof(string).Name} string.".Current();

Класс FormattableString уже содержит метод Invariant() , поэтому самым простым способом перехода на инвариантную культуру является using static :

using static System.FormattableString;

string invariant = Invariant($"Now = {DateTime.Now}"); string current = $"Now = {DateTime.Now}";


За кулисами

Интерполированные строки - это просто синтаксический сахар для String.Format() . Компилятор ( Roslyn ) превратит его в String.Format за кулисами:

var text = $"Hello {name + lastName}";

Вышеуказанное будет преобразовано в следующее:

string text = string.Format("Hello {0}", new object[] {
    name + lastName
});

Интерполяция строк и Linq

Можно использовать интерполированные строки в операторах Linq для дальнейшего повышения удобочитаемости.

var fooBar = (from DataRow x in fooBarTable.Rows
          select string.Format("{0}{1}", x["foo"], x["bar"])).ToList();

Может быть переписано как:

var fooBar = (from DataRow x in fooBarTable.Rows
          select $"{x["foo"]}{x["bar"]}").ToList();

Многоразовые интерполированные строки

С помощью string.Format вы можете создавать строки многократного использования:

public const string ErrorFormat = "Exception caught:\r\n{0}";

// ...

Logger.Log(string.Format(ErrorFormat, ex));

Интерполированные строки, однако, не будут компилироваться с заполнителями, ссылаясь на несуществующие переменные. Следующие не будут компилироваться:

public const string ErrorFormat = $"Exception caught:\r\n{error}";
// CS0103: The name 'error' does not exist in the current context

Вместо этого создайте Func<> который использует переменные и возвращает String :

public static Func<Exception, string> FormatError =
    error => $"Exception caught:\r\n{error}";

// ...

Logger.Log(FormatError(ex));

Строчная интерполяция и локализация

Если вы локализируете свое приложение, вы можете задаться вопросом, можно ли использовать интерполяцию строк вместе с локализацией. Действительно, было бы неплохо иметь возможность хранить в файлах ресурсов String s вроде:

"My name is {name} {middlename} {surname}"
вместо гораздо менее читаемого:

"My name is {0} {1} {2}"

Процесс интерполяции String происходит во время компиляции , в отличие от строки форматирования со строкой. string.Format которая встречается во время выполнения . Выражения в интерполированной строке должны ссылаться на имена в текущем контексте и должны храниться в файлах ресурсов. Это означает, что если вы хотите использовать локализацию, вам нужно сделать это так:

var FirstName = "John";

// method using different resource file "strings"
// for French ("strings.fr.resx"), German ("strings.de.resx"), 
// and English ("strings.en.resx")
void ShowMyNameLocalized(string name, string middlename = "", string surname = "")
{
    // get localized string
    var localizedMyNameIs = Properties.strings.Hello;
    // insert spaces where necessary
    name = (string.IsNullOrWhiteSpace(name) ? "" : name + " ");
    middlename = (string.IsNullOrWhiteSpace(middlename) ? "" : middlename + " ");
    surname = (string.IsNullOrWhiteSpace(surname) ? "" : surname + " ");
    // display it
    Console.WriteLine($"{localizedMyNameIs} {name}{middlename}{surname}".Trim());
}

// switch to French and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("fr-FR");
ShowMyNameLocalized(FirstName);

// switch to German and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("de-DE");
ShowMyNameLocalized(FirstName);

// switch to US English and greet John
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("en-US");
ShowMyNameLocalized(FirstName);

Если строки ресурсов для языков, используемых выше, правильно сохраняются в отдельных файлах ресурсов, вы должны получить следующий результат:

Bonjour, mon nom est John
Hallo, mein Имя ist John
Привет меня зовут Джон

Обратите внимание, что это означает, что имя следует за локализованной строкой на каждом языке. Если это не так, вам нужно добавить заполнители в строки ресурсов и изменить вышеприведенную функцию или вам нужно запросить информацию о культуре в этой функции и предоставить оператор case switch, содержащий разные случаи. Дополнительные сведения о файлах ресурсов см. В разделе Как использовать локализацию в C # .

Хорошей практикой является использование стандартного резервного языка, который большинство людей поймет, если перевод недоступен. Я предлагаю использовать английский язык по умолчанию.

Рекурсивная интерполяция

Хотя это и не очень полезно, разрешено использовать интерполированную string рекурсивно внутри фигурных скобок другого:

Console.WriteLine($"String has {$"My class is called {nameof(MyClass)}.".Length} chars:");
Console.WriteLine($"My class is called {nameof(MyClass)}.");

Выход:

Строка имеет 27 символов:

Мой класс называется MyClass.

Ожидание в улове и, наконец,

Можно использовать выражение await для применения оператора ожидания к задачам или задаче (из TResult) в catch и, finally блоках на C # 6.

Невозможно использовать выражение await в catch и, finally блоки в более ранних версиях из-за ограничений компилятора. C # 6 делает ожидание асинхронных задач намного проще, разрешая выражение await .

try
{
    //since C#5
    await service.InitializeAsync();
} 
catch (Exception e)
{
    //since C#6
    await logger.LogAsync(e);
}
finally
{
    //since C#6
    await service.CloseAsync();
}

В C # 5 требовалось использовать bool или объявить Exception вне try catch для выполнения асинхронных операций. Этот метод показан в следующем примере:

bool error = false;
Exception ex = null;

try
{
    // Since C#5
    await service.InitializeAsync();
} 
catch (Exception e)
{
    // Declare bool or place exception inside variable
    error = true;
    ex = e;
}

// If you don't use the exception
if (error)
{
    // Handle async task
}

// If want to use information from the exception
if (ex != null)
{
    await logger.LogAsync(e);
}    

// Close the service, since this isn't possible in the finally
await service.CloseAsync();

Нулевое распространение

?. оператор и оператор ?[...] называются нуль-условным оператором . Его иногда называют другие имена, такие как безопасный оператор навигации .

Это полезно, потому что если . (член accessor) применяется к выражению, которое вычисляет значение null , программа будет генерировать NullReferenceException . Если разработчик вместо этого использует ?. (нуль-условный) оператор, выражение будет вычисляться как null вместо того, чтобы бросать исключение.

Заметим, что если ?. используется оператор, а выражение не равно null ?. и . эквивалентны.


основы

var teacherName = classroom.GetTeacher().Name;
// throws NullReferenceException if GetTeacher() returns null

Посмотреть демо

Если classroom не имеет учителя, GetTeacher() может возвращать значение null . Когда он равен null и к нему будет применено свойство Name , будет NullReferenceException .

Если мы изменим это утверждение, чтобы использовать ?. синтаксис, результат всего выражения будет null :

var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null

Посмотреть демо

Впоследствии, если classroom также может быть null , мы могли бы также написать это утверждение как:

var teacherName = classroom?.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null OR classroom is null

Посмотреть демо

Это пример короткого замыкания: когда любая операция условного доступа, использующая оператор с нулевым условием, имеет значение null, все выражение немедленно вычисляется до нуля, без обработки остальной части цепочки.

Когда терминальный член выражения, содержащего оператор с нулевым условием, имеет тип значения, выражение оценивается как Nullable<T> этого типа и поэтому не может использоваться как прямая замена выражения без ?. ,

bool hasCertification = classroom.GetTeacher().HasCertification;
// compiles without error but may throw a NullReferenceException at runtime

bool hasCertification = classroom?.GetTeacher()?.HasCertification;
// compile time error: implicit conversion from bool? to bool not allowed

bool? hasCertification = classroom?.GetTeacher()?.HasCertification;
// works just fine, hasCertification will be null if any part of the chain is null

bool hasCertification = classroom?.GetTeacher()?.HasCertification.GetValueOrDefault();
// must extract value from nullable to assign to a value type variable

Использовать с Null-Coalescing Operator (??)

Вы можете комбинировать оператор с нулевым условием с Null-coalescing Operator ( ?? ), чтобы вернуть значение по умолчанию, если выражение разрешает null . Используя наш пример выше:

var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";
// teacherName will be "No Name" when GetTeacher() 
// returns null OR classroom is null OR Name is null

Использование с индексаторами

Оператор с нулевым условием может использоваться с индексаторами :

var firstStudentName = classroom?.Students?[0]?.Name;

В приведенном выше примере:

  • Первый ?. гарантирует, что classroom не является null .
  • Второй ? гарантирует, что вся коллекция Students не равна null .
  • Третий ?. после того, как индексатор гарантирует, что индексирующее устройство [0] не вернет null объект. Следует отметить, что эта операция все равно может вызвать IndexOutOfRangeException .

Использование с функциями void

Нуль-условный оператор также может использоваться с функциями void . Однако в этом случае оператор не будет оценивать значение null . Это просто предотвратит исключение NullReferenceException .

List<string> list = null;
list?.Add("hi");          // Does not evaluate to null

Использование с помощью вызова события

Предполагая следующее определение события:

private event EventArgs OnCompleted;

При вызове события традиционно лучше всего проверять, является ли событие null если нет подписчиков:

var handler = OnCompleted;
if (handler != null)
{
    handler(EventArgs.Empty);
}

Поскольку введен оператор с нулевым условием, вызов можно свести к одной строке:

OnCompleted?.Invoke(EventArgs.Empty);

Ограничения

Null-условный оператор производит rvalue, а не lvalue, то есть он не может использоваться для присвоения свойств, подписки на события и т. Д. Например, следующий код не будет работать:

// Error: The left-hand side of an assignment must be a variable, property or indexer
Process.GetProcessById(1337)?.EnableRaisingEvents = true;
// Error: The event can only appear on the left hand side of += or -=
Process.GetProcessById(1337)?.Exited += OnProcessExited;

Gotchas

Обратите внимание, что:

int? nameLength = person?.Name.Length;    // safe if 'person' is null

это не то же самое, что:

int? nameLength = (person?.Name).Length;  // avoid this

потому что первое соответствует:

int? nameLength = person != null ? (int?)person.Name.Length : null;

и последнее соответствует:

int? nameLength = (person != null ? person.Name : null).Length;

Несмотря на тернарный оператор ?: Используется здесь для объяснения разницы между двумя случаями, эти операторы не эквивалентны. Это можно легко продемонстрировать в следующем примере:

void Main()
{
    var foo = new Foo();
    Console.WriteLine("Null propagation");
    Console.WriteLine(foo.Bar?.Length);

    Console.WriteLine("Ternary");
    Console.WriteLine(foo.Bar != null ? foo.Bar.Length : (int?)null);
}

class Foo
{
    public string Bar
    {
        get
        {
            Console.WriteLine("I was read");
            return string.Empty;
        }
    }
}

Какие результаты:

Нулевое распространение
Я был прочитан
0
троичный
Я был прочитан
Я был прочитан
0

Посмотреть демо

Чтобы избежать эквивалента нескольких вызовов, можно:

var interimResult = foo.Bar;
Console.WriteLine(interimResult != null ? interimResult.Length : (int?)null);

И эта разница несколько объясняет, почему оператор распространения пустоты еще не поддерживается в деревьях выражений.

Использование статического типа

using static [Namespace.Type] директивы позволяет импортировать статические элементы типов и значений перечисления. Методы расширения импортируются как методы расширения (всего один тип), а не в область верхнего уровня.

6,0
using static System.Console;
using static System.ConsoleColor;
using static System.Math;

class Program
{
    static void Main()
    {
        BackgroundColor = DarkBlue;
        WriteLine(Sqrt(2));
    }
}

Демо-скрипты

6,0
using System;

class Program
{
    static void Main()
    {
        Console.BackgroundColor = ConsoleColor.DarkBlue;
        Console.WriteLine(Math.Sqrt(2));
    }
}

Улучшенное разрешение перегрузки

Следующий фрагмент показывает пример передачи группы методов (в отличие от лямбда), когда ожидается делегат. Разрешение перегрузки теперь разрешит это вместо повышения неоднозначной ошибки перегрузки из-за возможности C # 6 проверить тип возвращаемого метода.

using System;
public class Program
{
    public static void Main()
    {
        Overloaded(DoSomething);
    }

    static void Overloaded(Action action)
    {
       Console.WriteLine("overload with action called");
    }

    static void Overloaded(Func<int> function)
    {
       Console.WriteLine("overload with Func<int> called");
    }

    static int DoSomething()
    {
        Console.WriteLine(0);
        return 0;
    }
}

Результаты:

6,0

Выход

перегрузка с помощью Func <int>

Посмотреть демо

5.0

ошибка

ошибка CS0121: вызов неоднозначен между следующими методами или свойствами: «Program.Overloaded (System.ction)» и «Program.Overloaded (System.Func)»

C # 6 также может хорошо обработать следующий случай точного соответствия для лямбда-выражений, что привело бы к ошибке в C # 5 .

using System;

class Program
{
    static void Foo(Func<Func<long>> func) {}
    static void Foo(Func<Func<int>> func) {}

    static void Main()
    {
        Foo(() => () => 7);
    }
}

Незначительные изменения и исправления

Круглые скобки теперь запрещены по именованным параметрам. Следующие компиляции в C # 5, но не C # 6

5.0
Console.WriteLine((value: 23));

Операнды is и as больше не могут быть группами методов. Следующие компиляции в C # 5, но не C # 6

5.0
var result = "".Any is byte;

Собственный компилятор допустил это (хотя он и сделал предупреждение), и на самом деле даже не проверял совместимость метода расширения, позволяя сумасшедшие вещи, такие как 1.Any is string или IDisposable.Dispose is object .

См. Эту ссылку для получения обновлений об изменениях.

Использование метода расширения для инициализации коллекции

Синтаксис инициализации синтаксиса можно использовать при создании экземпляра любого класса, который реализует IEnumerable и имеет метод Add который принимает один параметр.

В предыдущих версиях этот метод Add должен был быть методом экземпляра для инициализированного класса. В C # 6 он также может быть методом расширения.

public class CollectionWithAdd : IEnumerable
{
    public void Add<T>(T item)
    {
        Console.WriteLine("Item added with instance add method: " + item);
    }

    public IEnumerator GetEnumerator()
    {
        // Some implementation here
    }
}

public class CollectionWithoutAdd : IEnumerable
{
    public IEnumerator GetEnumerator()
    {
        // Some implementation here
    }
}

public static class Extensions
{
    public static void Add<T>(this CollectionWithoutAdd collection, T item)
    {
        Console.WriteLine("Item added with extension add method: " + item);
    }
}

public class Program
{
    public static void Main()
    {
        var collection1 = new CollectionWithAdd{1,2,3}; // Valid in all C# versions
        var collection2 = new CollectionWithoutAdd{4,5,6}; // Valid only since C# 6
    }
}

Это приведет к выводу:

Элемент добавлен с помощью метода добавления экземпляра: 1
Элемент добавлен с помощью метода добавления экземпляра: 2
Элемент добавлен с помощью метода добавления экземпляра: 3
Добавлен элемент с добавлением метода добавления: 4
Добавлен элемент с добавлением метода добавления: 5
Добавлен элемент с добавлением метода добавления: 6

Отключить улучшения предупреждений

В C # 5.0 и ранее разработчик мог только подавлять предупреждения по номеру. С введением Roslyn Analyzers C # нужен способ отключения предупреждений, выпущенных из конкретных библиотек. С C # 6.0 директива pragma может подавлять предупреждения по имени.

До:

#pragma warning disable 0501

C # 6.0:

#pragma warning disable CS0501


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow