C# Language
Возможности C # 7.0
Поиск…
Вступление
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, вы должны объявить переменную для получения значения перед вызовом функции:
int value;
if (int.TryParse(input, out value))
{
Foo(value); // ok
}
else
{
Foo(value); // value is zero
}
Foo(value); // ok
В C # 7.0 вы можете вставить объявление переменной, переданной в параметр out
, исключая необходимость в отдельном объявлении переменной:
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
заключается в том, что ее можно использовать с анонимными типами.
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;
Где _
Разделитель цифр не может быть использован:
- в начале значения (
_121
) - в конце значения (
121_
или121.05_
) - рядом с десятичной (
10_.0
) - рядом с символом экспоненты (
1.1e_1
) - рядом с спецификатором типа (
10_f
) - сразу после
0x
или0b
в двоичных и шестнадцатеричных литералах ( может быть изменено, чтобы разрешить, например, 0b_1001_1000 )
Языковая поддержка для кортежей
основы
Кортеж представляет собой упорядоченный конечный список элементов. Кортежи обычно используются в программировании как средство совместной работы с одним отдельным объектом вместо индивидуальной работы с каждым из элементов кортежа и для представления отдельных строк (т. Е. «Записей») в реляционной базе данных.
В 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 ) |
Рекомендации
- Предложение оригинального набора языков для GitHub
- Реализованное решение VS 15 для функций C # 7.0
- Пакет NuGet Tuple
Локальные функции
Локальные функции определяются внутри метода и недоступны вне его. Они имеют доступ ко всем локальным переменным и поддерживают итераторы, синтаксис 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
для проверки типа и объявления новой переменной одновременно.
пример
string s = o as string;
if(s != null)
{
// do something with s
}
можно переписать как:
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