Поиск…


Вступление

C # 7.0 - седьмая версия C #. Эта версия содержит некоторые новые функции: поддержка языков для Tuples, локальных функций, out var , разделителей цифр, двоичных литералов, сопоставления шаблонов, выражений throw, ref return и ref local и extended expression bodied members list.

Официальная ссылка: Что нового в C # 7

декларация var

Обычным шаблоном в C # является использование bool TryParse(object input, out object value) для безопасного анализа объектов.

Объявление out var является простой функцией улучшения удобочитаемости. Он позволяет объявлять переменную в то же самое время, которое передается как параметр out.

Переменная, объявленная таким образом, привязана к остальной части тела в точке, в которой она объявлена.

пример

Используя TryParse до C # 7.0, вы должны объявить переменную для получения значения перед вызовом функции:

7,0
int value;
if (int.TryParse(input, out value)) 
{
    Foo(value); // ok
}
else
{
    Foo(value); // value is zero
}

Foo(value); // ok

В C # 7.0 вы можете вставить объявление переменной, переданной в параметр out , исключая необходимость в отдельном объявлении переменной:

7,0
if (int.TryParse(input, out var value)) 
{
    Foo(value); // ok
}
else
{
    Foo(value); // value is zero
}

Foo(value); // still ok, the value in scope within the remainder of the body

Если некоторые из параметров, возвращаемых функцией out не нужны, вы можете использовать оператор discard _ .

p.GetCoordinates(out var x, out _); // I only care about x

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

Другая особенность объявления out var заключается в том, что ее можно использовать с анонимными типами.

7,0
var a = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var groupedByMod2 = a.Select(x => new
                                  {
                                      Source = x,
                                      Mod2 = x % 2
                                  })
                     .GroupBy(x => x.Mod2)
                     .ToDictionary(g => g.Key, g => g.ToArray());
if (groupedByMod2.TryGetValue(1, out var oddElements))
{
    Console.WriteLine(oddElements.Length);
}

В этом коде мы создаем Dictionary с ключом int и массив анонимного значения типа. В предыдущей версии C # было невозможно использовать метод TryGetValue потому что вам нужно было объявить переменную out (которая имеет анонимный тип!). Однако, out var нам не нужно явно указывать тип переменной out .

Ограничения

Обратите внимание, что объявления var var ограничены в запросах LINQ, поскольку выражения интерпретируются как выражения лямбда-тел, поэтому объем вводимых переменных ограничен этими лямбдами. Например, следующий код не будет работать:

var nums = 
    from item in seq
    let success = int.TryParse(item, out var tmp)
    select success ? tmp : 0; // Error: The name 'tmp' does not exist in the current context

Рекомендации

Бинарные литералы

Префикс 0b может использоваться для представления бинарных литералов.

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

Ниже приведены эквивалентные способы указания int со значением 34 (= 2 5 + 2 1 ):

// Using a binary literal:
//   bits: 76543210
int a1 = 0b00100010;          // binary: explicitly specify bits

// Existing methods:
int a2 = 0x22;                // hexadecimal: every digit corresponds to 4 bits
int a3 = 34;                  // decimal: hard to visualise which bits are set
int a4 = (1 << 5) | (1 << 1); // bitwise arithmetic: combining non-zero bits

Перечисления флагов

Раньше указание значений флага для enum могло быть выполнено только с использованием одного из трех методов в этом примере:

[Flags]
public enum DaysOfWeek
{
    // Previously available methods:
    //          decimal        hex       bit shifting
    Monday    =  1,    //    = 0x01    = 1 << 0
    Tuesday   =  2,    //    = 0x02    = 1 << 1
    Wednesday =  4,    //    = 0x04    = 1 << 2
    Thursday  =  8,    //    = 0x08    = 1 << 3
    Friday    = 16,    //    = 0x10    = 1 << 4
    Saturday  = 32,    //    = 0x20    = 1 << 5
    Sunday    = 64,    //    = 0x40    = 1 << 6

    Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
    Weekends = Saturday | Sunday
}

С бинарными литералами более очевидно, какие биты установлены, и использование их не требует понимания шестнадцатеричных чисел и побитовой арифметики:

[Flags]
public enum DaysOfWeek
{
    Monday    = 0b00000001,
    Tuesday   = 0b00000010,
    Wednesday = 0b00000100,
    Thursday  = 0b00001000,
    Friday    = 0b00010000,
    Saturday  = 0b00100000,
    Sunday    = 0b01000000,

    Weekdays = Monday | Tuesday | Wednesday | Thursday | Friday,
    Weekends = Saturday | Sunday
}

Цифровые разделители

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

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

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

int bin = 0b1001_1010_0001_0100;
int hex = 0x1b_a0_44_fe;
int dec = 33_554_432;
int weird = 1_2__3___4____5_____6______7_______8________9;
double real = 1_000.111_1e-1_000;

Где _ Разделитель цифр не может быть использован:

Языковая поддержка для кортежей

основы

Кортеж представляет собой упорядоченный конечный список элементов. Кортежи обычно используются в программировании как средство совместной работы с одним отдельным объектом вместо индивидуальной работы с каждым из элементов кортежа и для представления отдельных строк (т. Е. «Записей») в реляционной базе данных.

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

public (int sum, int count) GetTallies() 
{
    return (1, 2);
}

Замечание : для этого в Visual Studio 2017 вам необходимо получить пакет System.ValueTuple .

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

var result = GetTallies();
// > result.sum
// 1
// > result.count
// 2

Деконструкция кортежа

Деконструкция кортежа разделяет кортеж на его части.

Например, GetTallies и присвоение возвращаемого значения двум отдельным переменным деконструирует кортеж для этих двух переменных:

(int tallyOne, int tallyTwo) = GetTallies();

var также работает:

(var s, var c) = GetTallies();

Вы также можете использовать более короткий синтаксис, с var вне () :

var (s, c) = GetTallies();

Вы также можете деконструировать существующие переменные:

int s, c;
(s, c) = GetTallies();

Обмен теперь намного проще (без переменной temp):

(b, a) = (a, b);

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

class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public void Deconstruct(out string firstName, out string lastName)
    {
        firstName = FirstName;
        lastName = LastName;
    }
}

var person = new Person { FirstName = "John", LastName = "Smith" };
var (localFirstName, localLastName) = person;

В этом случае синтаксис (localFirstName, localLastName) = person вызывает (localFirstName, localLastName) = person Deconstruct на person .

Деконструкцию можно даже определить в методе расширения. Это эквивалентно приведенному выше:

public static class PersonExtensions
{
    public static void Deconstruct(this Person person, out string firstName, out string lastName)
    {
        firstName = person.FirstName;
        lastName = person.LastName;
    }
}

var (localFirstName, localLastName) = person;

Альтернативным подходом для класса Person является определение самого Name как Tuple . Рассмотрим следующее:

class Person
{
    public (string First, string Last) Name { get; }

    public Person((string FirstName, string LastName) name)
    {
        Name = name;
    }
}

Тогда вы можете создать экземпляр человека таким образом (где мы можем взять кортеж в качестве аргумента):

var person = new Person(("Jane", "Smith"));

var firstName = person.Name.First; // "Jane"
var lastName = person.Name.Last;   // "Smith"

Инициализация кортежа

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

var name = ("John", "Smith");
Console.WriteLine(name.Item1);
// Outputs John

Console.WriteLine(name.Item2);
// Outputs Smith

При создании кортежа вы можете назначать имена объявлений ad-hoc членам кортежа:

var name = (first: "John", middle: "Q", last: "Smith");
Console.WriteLine(name.first);
// Outputs John

Вывод типа

Множественные кортежи, определенные с одной и той же сигнатурой (совпадающие типы и количество), будут выведены как соответствующие типы. Например:

public (int sum, double average) Measure(List<int> items)
{
    var stats = (sum: 0, average: 0d);
    stats.sum = items.Sum();
    stats.average = items.Average();
    return stats;
}

stats может быть возвращена, так как объявление переменной stats и ответная подпись метода совпадают.

Имена полей отражения и кортежа

Имена членов не существуют во время выполнения. Отражение будет рассматривать кортежи с одинаковым числом и типами членов одинаково, даже если имена участников не совпадают. Преобразование кортежа в object а затем в кортеж с теми же типами членов, но с разными именами, также не вызывает исключения.

Хотя сам класс ValueTuple не сохраняет информацию для имен членов, информация доступна через отражение в TupleElementNamesAttribute. Этот атрибут не применяется к самому кортежу, а к параметрам метода, возвращаемым значениям, свойствам и полям. Это позволяет сохранять имена элементов кортежа в сборках, т. Е. Если метод возвращает (имя строки, int count), имя и количество имен будут доступны вызывающим лицам метода в другой сборке, потому что возвращаемое значение будет отмечено с помощью TupleElementNameAttribute, содержащим значения «имя» и «счет».

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

Новые функции кортежа (с использованием базового типа ValueTuple ) полностью поддерживают дженерики и могут использоваться как общий тип параметра. Это позволяет использовать их с шаблоном async / await :

public async Task<(string value, int count)> GetValueAsync()
{
    string fooBar = await _stackoverflow.GetStringAsync();
    int num = await _stackoverflow.GetIntAsync();

    return (fooBar, num);
}

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

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

Пример:

private readonly List<Tuple<string, string, string>> labels = new List<Tuple<string, string, string>>()
{
    new Tuple<string, string, string>("test1", "test2", "Value"),
    new Tuple<string, string, string>("test1", "test1", "Value2"),
    new Tuple<string, string, string>("test2", "test2", "Value3"),
};

public string FindMatchingValue(string firstElement, string secondElement)
{
    var result = labels
        .Where(w => w.Item1 == firstElement && w.Item2 == secondElement)
        .FirstOrDefault();

    if (result == null)
        throw new ArgumentException("combo not found");

    return result.Item3;
}

С новыми кортежами могут стать:

private readonly List<(string firstThingy, string secondThingyLabel, string foundValue)> labels = new List<(string firstThingy, string secondThingyLabel, string foundValue)>()
{
    ("test1", "test2", "Value"),
    ("test1", "test1", "Value2"),
    ("test2", "test2", "Value3"),
}

public string FindMatchingValue(string firstElement, string secondElement)
{
    var result = labels
        .Where(w => w.firstThingy == firstElement && w.secondThingyLabel == secondElement)
        .FirstOrDefault();

    if (result == null)
        throw new ArgumentException("combo not found");

    return result.foundValue;
}

Хотя именование на примере кортежа выше является довольно общим, идея соответствующих ярлыков позволяет глубже понять, что делается в коде, ссылаясь на «item1», «item2» и «item3».

Различия между ValueTuple и Tuple

Основной причиной внедрения ValueTuple является производительность.

Название типа ValueTuple Tuple
Класс или структура struct class
Мутируемость (изменение значений после создания) изменчивый неизменный
Имена членов и поддержка других языков да нет ( TBD )

Рекомендации

Локальные функции

Локальные функции определяются внутри метода и недоступны вне его. Они имеют доступ ко всем локальным переменным и поддерживают итераторы, синтаксис async / await и лямбда. Таким образом, повторения, специфичные для функции, могут функционировать без перенаселения класса. Как побочный эффект, это улучшает производительность предложения intellisense.

пример

double GetCylinderVolume(double radius, double height)
{
    return getVolume();

    double getVolume()
    {
        // You can declare inner-local functions in a local function 
        double GetCircleArea(double r) => Math.PI * r * r;

        // ALL parents' variables are accessible even though parent doesn't have any input. 
        return GetCircleArea(radius) * height;
    }
}

Локальные функции значительно упрощают код для операторов LINQ, где вам обычно приходится отделять проверки аргументов от фактической логики, чтобы мгновенно проверять аргументы, а не откладываться до начала итерации.

пример

public static IEnumerable<TSource> Where<TSource>(
    this IEnumerable<TSource> source, 
    Func<TSource, bool> predicate)
{
    if (source == null) throw new ArgumentNullException(nameof(source));
    if (predicate == null) throw new ArgumentNullException(nameof(predicate));

    return iterator();

    IEnumerable<TSource> iterator()
    {
        foreach (TSource element in source)
            if (predicate(element))
                yield return element;
    }
}

Локальные функции также поддерживают ключевые слова async и await .

пример

async Task WriteEmailsAsync()
{
    var emailRegex = new Regex(@"(?i)[a-z0-9_.+-]+@[a-z0-9-]+\.[a-z0-9-.]+");
    IEnumerable<string> emails1 = await getEmailsFromFileAsync("input1.txt");
    IEnumerable<string> emails2 = await getEmailsFromFileAsync("input2.txt");
    await writeLinesToFileAsync(emails1.Concat(emails2), "output.txt");

    async Task<IEnumerable<string>> getEmailsFromFileAsync(string fileName)
    {
        string text;

        using (StreamReader reader = File.OpenText(fileName))
        {
            text = await reader.ReadToEndAsync();
        }

        return from Match emailMatch in emailRegex.Matches(text) select emailMatch.Value;
    }

    async Task writeLinesToFileAsync(IEnumerable<string> lines, string fileName)
    {
        using (StreamWriter writer = File.CreateText(fileName))
        {
            foreach (string line in lines)
            {
                await writer.WriteLineAsync(line);
            }
        }
    }
}

Одна важная вещь, которую вы, возможно, заметили, заключается в том, что локальные функции могут быть определены в операторе return , их не нужно определять над ним. Кроме того, локальные функции обычно следуют принципу именования «lowerCamelCase», чтобы более легко отличить себя от функций класса.

Соответствие шаблону

Расширения соответствия шаблонов для C # позволяют использовать многие из преимуществ сопоставления шаблонов с функциональных языков, но таким образом, что плавно интегрируется с ощущением основного языка

выражение switch

Поиск по шаблону расширяет switch заявление на включение типов:

class Geometry {} 

class Triangle : Geometry
{
    public int Width { get; set; }
    public int Height { get; set; }
    public int Base { get; set; }
}

class Rectangle : Geometry
{
    public int Width { get; set; }
    public int Height { get; set; }
}

class Square : Geometry
{
    public int Width { get; set; }
}

public static void PatternMatching()
{
    Geometry g = new Square { Width = 5 }; 
    
    switch (g)
    {
        case Triangle t:
            Console.WriteLine($"{t.Width} {t.Height} {t.Base}");
            break;
        case Rectangle sq when sq.Width == sq.Height:
            Console.WriteLine($"Square rectangle: {sq.Width} {sq.Height}");
            break;
        case Rectangle r:
            Console.WriteLine($"{r.Width} {r.Height}");
            break;
        case Square s:
            Console.WriteLine($"{s.Width}");
            break;
        default:
            Console.WriteLine("<other>");
            break;
    }
}

is выражением

Соответствие шаблону расширяет оператор is для проверки типа и объявления новой переменной одновременно.

пример

7,0
string s = o as string;
if(s != null)
{
    // do something with s
}

можно переписать как:

7,0
if(o is string s)
{
    //Do something with s
};

Также обратите внимание, что область действия переменной шаблона s распространяется за пределы блока if достигающего конца охватывающей области, например:

if(someCondition)
{
   if(o is string s)
   {
      //Do something with s
   }
   else
   {
     // s is unassigned here, but accessible 
   }

   // s is unassigned here, but accessible 
}
// s is not accessible here

ref возвратить и указать местный

Ref возвращает и ref locals полезны для манипулирования и возврата ссылок на блоки памяти вместо копирования памяти, не прибегая к небезопасным указателям.

Вернуться

public static ref TValue Choose<TValue>(
    Func<bool> condition, ref TValue left, ref TValue right)
{
    return condition() ? ref left : ref right;
}

При этом вы можете передать два значения по ссылке, при этом один из них будет возвращен на основе некоторого условия:

Matrix3D left = …, right = …;
Choose(chooser, ref left, ref right).M20 = 1.0;

Ссылка

public static ref int Max(ref int first, ref int second, ref int third)
{
    ref int max = first > second ? ref first : ref second;
    return max > third ? ref max : ref third;
}
…
int a = 1, b = 2, c = 3;
Max(ref a, ref b, ref c) = 4;
Debug.Assert(a == 1); // true
Debug.Assert(b == 2); // true
Debug.Assert(c == 4); // true

Небезопасные операции ref

В System.Runtime.CompilerServices.Unsafe определен набор небезопасных операций, которые позволяют вам манипулировать значениями ref как если бы они были указателями, в основном.

Например, переинтерпретировать адрес памяти ( ref ) как другой тип:

byte[] b = new byte[4] { 0x42, 0x42, 0x42, 0x42 };

ref int r = ref Unsafe.As<byte, int>(ref b[0]);
Assert.Equal(0x42424242, r);

0x0EF00EF0;
Assert.Equal(0xFE, b[0] | b[1] | b[2] | b[3]);

Остерегайтесь энтианности при этом, например, проверьте BitConverter.IsLittleEndian если необходимо, и обработайте соответствующим образом.

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

int[] a = new int[] { 0x123, 0x234, 0x345, 0x456 };

ref int r1 = ref Unsafe.Add(ref a[0], 1);
Assert.Equal(0x234, r1);

ref int r2 = ref Unsafe.Add(ref r1, 2);
Assert.Equal(0x456, r2);

ref int r3 = ref Unsafe.Add(ref r2, -3);
Assert.Equal(0x123, r3);

Или аналогичный Subtract :

string[] a = new string[] { "abc", "def", "ghi", "jkl" };

ref string r1 = ref Unsafe.Subtract(ref a[0], -2);
Assert.Equal("ghi", r1);

ref string r2 = ref Unsafe.Subtract(ref r1, -1);
Assert.Equal("jkl", r2);

ref string r3 = ref Unsafe.Subtract(ref r2, 3);
Assert.Equal("abc", r3);

Кроме того, можно проверить, совпадают ли два значения ref , т.е. тот же адрес:

long[] a = new long[2];

Assert.True(Unsafe.AreSame(ref a[0], ref a[0]));
Assert.False(Unsafe.AreSame(ref a[0], ref a[1]));

связи

Рослин Гитуб

System.Runtime.CompilerServices.Unsafe on github

вызывать выражения

C # 7.0 позволяет метать как выражение в определенных местах:

class Person
{
    public string Name { get; }

    public Person(string name) => Name = name ?? throw new ArgumentNullException(nameof(name));

    public string GetFirstName()
    {
        var parts = Name.Split(' ');
        return (parts.Length > 0) ? parts[0] : throw new InvalidOperationException("No name!");
    }

    public string GetLastName() => throw new NotImplementedException();
}

До C # 7.0, если вы хотите выбросить исключение из тела выражения, вам необходимо:

var spoons = "dinner,desert,soup".Split(',');

var spoonsArray = spoons.Length > 0 ? spoons : null;

if (spoonsArray == null) 
{
    throw new Exception("There are no spoons");
}

Или же

var spoonsArray = spoons.Length > 0 
    ? spoons 
    : new Func<string[]>(() => 
      {
          throw new Exception("There are no spoons");
      })();

В C # 7.0 приведенное выше упрощено:

var spoonsArray = spoons.Length > 0 ? spoons : throw new Exception("There are no spoons");

Список расширенных выражений

C # 7.0 добавляет аксессоры, конструкторы и финализаторы в список вещей, которые могут иметь тела выражений:

class Person
{
    private static ConcurrentDictionary<int, string> names = new ConcurrentDictionary<int, string>();

    private int id = GetId();

    public Person(string name) => names.TryAdd(id, name); // constructors

    ~Person() => names.TryRemove(id, out _);              // finalizers

    public string Name
    {
        get => names[id];                                 // getters
        set => names[id] = value;                         // setters
    }
}

Также см. Раздел объявления var var для оператора discard.

ValueTask

Task<T> - это класс и вызывает ненужные накладные расходы на его распределение, когда результат сразу доступен.

ValueTask<T> является структурой и был введен для предотвращения выделения объекта Task в случае, если результат операции async уже доступен во время ожидания.

Итак, ValueTask<T> предоставляет два преимущества:

1. Увеличение производительности

Вот пример Task<T> :

  • Требуется распределение кучи
  • Принимает 120 нс с JIT
async Task<int> TestTask(int d)
{
    await Task.Delay(d);
    return 10;
}

Вот ValueTask<T> аналогового значения ValueTask<T> :

  • Нет распределение кучи , если результат известен синхронно (что не в этом случае из-за Task.Delay , но часто не во многих реальных async / await сценариев)
  • Принимает 65ns с JIT
async ValueTask<int> TestValueTask(int d)
{
    await Task.Delay(d);
    return 10;
}

2. Повышенная гибкость внедрения

Реализация асинхронного интерфейса, желающего быть синхронным, в противном случае была бы вынуждена использовать либо Task.Run либо Task.FromResult (что привело к Task.FromResult производительности, описанному выше). Таким образом, существует некоторое давление на синхронные реализации.

Но с ValueTask<T> реализации более свободны выбирать между синхронными или асинхронными, не затрагивая вызывающих.

Например, вот интерфейс с асинхронным методом:

interface IFoo<T>
{
    ValueTask<T> BarAsync();
}

... и вот как можно вызвать этот метод:

IFoo<T> thing = getThing();
var x = await thing.BarAsync();

С ValueTask приведенный выше код будет работать либо с синхронными, либо с асинхронными реализациями :

Синхронная реализация:

class SynchronousFoo<T> : IFoo<T>
{
    public ValueTask<T> BarAsync()
    {
        var value = default(T);
        return new ValueTask<T>(value);
    }
}

Асинхронная реализация

class AsynchronousFoo<T> : IFoo<T>
{
    public async ValueTask<T> BarAsync()
    {
        var value = default(T);
        await Task.Delay(1);
        return value;
    }
}

Заметки

Несмотря на то, что в C # 7.0 планировалось добавить конструкцию ValueTask , она пока сохраняется как еще одна библиотека. Пакет ValueTask <T> System.Threading.Tasks.Extensions можно загрузить из галереи Nuget



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