Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом документе перечислены известные критические изменения в Roslyn после общего выпуска .NET 9 (пакет SDK для .NET версии 9.0.100) до общего выпуска .NET 10 (пакет SDK для .NET версии 10.0.100).
scoped
в списке лямбда-параметров теперь всегда является модификатором.
Введено в Visual Studio 2022 версии 17.13
C# 14 представляет возможность записи лямбда-модуля с модификаторами параметров, не указывая тип параметра: https://github.com/dotnet/csharplang/blob/main/proposals/simple-lambda-parameters-with-modifiers.md
В рамках этой работы было принято ломающее изменение, согласно которому scoped
всегда будет выступать в роли модификатора в параметре лямбда, даже если ранее он мог приниматься как имя типа. Например:
var v = (scoped scoped s) => { ... };
ref struct @scoped { }
В C# 14 это будет ошибка, так как оба маркера scoped
рассматриваются как модификаторы. Обходной путь — использовать @
в позиции имени типа следующим образом:
var v = (scoped @scoped s) => { ... };
ref struct @scoped { }
Span<T>
и ReadOnlySpan<T>
перегрузки применимы в большем количестве сценариев в C# 14 и новее
Введено в Visual Studio 2022 версии 17.13
C# 14 содержит новые встроенные преобразования диапазонов и правила вывода типов. Это означает, что различные перегрузки могут быть выбраны по сравнению с C# 13, а иногда может возникнуть неоднозначность во время компиляции, так как новая перегрузка применима, но нет одной лучшей перегрузки.
В следующем примере показаны некоторые неоднозначности и возможные обходные пути.
Обратите внимание, что еще одно решение заключается в том, чтобы авторы API использовали OverloadResolutionPriorityAttribute
.
var x = new long[] { 1 };
Assert.Equal([2], x); // previously Assert.Equal<T>(T[], T[]), now ambiguous with Assert.Equal<T>(ReadOnlySpan<T>, Span<T>)
Assert.Equal([2], x.AsSpan()); // workaround
var y = new int[] { 1, 2 };
var s = new ArraySegment<int>(y, 1, 1);
Assert.Equal(y, s); // previously Assert.Equal<T>(T, T), now ambiguous with Assert.Equal<T>(Span<T>, Span<T>)
Assert.Equal(y.AsSpan(), s); // workaround
В C# 14 может быть выбрана Span<T>
перегрузка в тех случаях, когда в C# 13 выбиралась перегрузка, принимающая интерфейс, реализуемый T[]
(например, IEnumerable<T>
). Это может привести к ArrayTypeMismatchException
во время выполнения при использовании с ковариантным массивом.
string[] s = new[] { "a" };
object[] o = s; // array variance
C.R(o); // wrote 1 previously, now crashes in Span<T> constructor with ArrayTypeMismatchException
C.R(o.AsEnumerable()); // workaround
static class C
{
public static void R<T>(IEnumerable<T> e) => Console.Write(1);
public static void R<T>(Span<T> s) => Console.Write(2);
// another workaround:
public static void R<T>(ReadOnlySpan<T> s) => Console.Write(3);
}
По этой причине ReadOnlySpan<T>
обычно предпочтительнее Span<T>
в процессе разрешения перегрузок в C# 14.
В некоторых случаях это может привести к ошибкам компиляции, например, когда существуют перегрузки для Span<T>
и ReadOnlySpan<T>
, которые принимают и возвращают один и тот же тип диапазона.
double[] x = new double[0];
Span<ulong> y = MemoryMarshal.Cast<double, ulong>(x); // previously worked, now compilation error
Span<ulong> z = MemoryMarshal.Cast<double, ulong>(x.AsSpan()); // workaround
static class MemoryMarshal
{
public static ReadOnlySpan<TTo> Cast<TFrom, TTo>(ReadOnlySpan<TFrom> span) => default;
public static Span<TTo> Cast<TFrom, TTo>(Span<TFrom> span) => default;
}
При использовании C# 14 или более поздней версии и нацеливания на более старую версию .NET, чем net10.0
, или .NET Framework со ссылкой на System.Memory
, возникает критическое изменение в отношении Enumerable.Reverse
и массивов:
int[] x = new[] { 1, 2, 3 };
var y = x.Reverse(); // previously Enumerable.Reverse, now MemoryExtensions.Reverse
На net10.0
есть Enumerable.Reverse(this T[])
, которая имеет приоритет, и поэтому удается избежать разрыва.
В противном случае MemoryExtensions.Reverse(this Span<T>)
интерпретируется с семантикой, отличной от Enumerable.Reverse(this IEnumerable<T>)
(которая разрешалась в C# 13 и более ранних версиях).
В частности, расширение Span
выполняет разворот на месте и возвращает void
.
В качестве обходного решения можно определить собственные Enumerable.Reverse(this T[])
или использовать Enumerable.Reverse
явным образом:
int[] x = new[] { 1, 2, 3 };
var y = Enumerable.Reverse(x); // instead of 'x.Reverse();'
Диагностика теперь осуществляется для метода удаления, основанного на шаблонах, в foreach
Введено в Visual Studio 2022 версии 17.13
Например, устаревший метод DisposeAsync
теперь сообщается в await foreach
.
await foreach (var i in new C()) { } // 'C.AsyncEnumerator.DisposeAsync()' is obsolete
class C
{
public AsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default)
{
throw null;
}
public sealed class AsyncEnumerator : System.IAsyncDisposable
{
public int Current { get => throw null; }
public Task<bool> MoveNextAsync() => throw null;
[System.Obsolete]
public ValueTask DisposeAsync() => throw null;
}
}
Задайте для объекта перечислителя значение "after" во время удаления
Введено в Visual Studio 2022 версии 17.13
Компьютер состояния для перечислителей неправильно разрешил повторное выполнение после удаления перечислителя.
Теперь MoveNext()
в объекте перечислителя, который уже был удалён, правильно возвращает false
без выполнения дополнительного пользовательского кода.
var enumerator = C.GetEnumerator();
Console.Write(enumerator.MoveNext()); // prints True
Console.Write(enumerator.Current); // prints 1
enumerator.Dispose();
Console.Write(enumerator.MoveNext()); // now prints False
class C
{
public static IEnumerator<int> GetEnumerator()
{
yield return 1;
Console.Write("not executed after disposal")
yield return 2;
}
}
UnscopedRefAttribute
нельзя использовать с устаревшими правилами безопасности ссылок
Введено в Visual Studio 2022 версии 17.13
UnscopedRefAttribute
непреднамеренно повлиял на код, скомпилированный новыми версиями компилятора Roslyn, даже если этот код был скомпилирован по более ранним правилам безопасности ссылок, то есть для C# 10 или более ранних версий с использованием net6.0 или более ранней.
Однако атрибут не должен иметь эффект в этом контексте, и это теперь исправлено.
Код, который ранее не выдавал ошибок в C# 10 или более ранних версиях с net6.0 или более ранними версиями, теперь может не компилироваться.
using System.Diagnostics.CodeAnalysis;
struct S
{
public int F;
// previously allowed in C# 10 with net6.0
// now fails with the same error as if the [UnscopedRef] wasn't there:
// error CS8170: Struct members cannot return 'this' or other instance members by reference
[UnscopedRef] public ref int Ref() => ref F;
}
Чтобы предотвратить недоразумения (когда предполагается, что атрибут оказывает эффект, но на самом деле это не так, поскольку код компилируется с использованием более ранних правил безопасного обращения с ссылками), выдается предупреждение при использовании атрибута в C# 10 или более ранних версиях вместе с net6.0 или более ранними версиями.
using System.Diagnostics.CodeAnalysis;
struct S
{
// both are errors in C# 10 with net6.0:
// warning CS9269: UnscopedRefAttribute is only valid in C# 11 or later or when targeting net7.0 or later.
[UnscopedRef] public ref int Ref() => throw null!;
public static void M([UnscopedRef] ref int x) { }
}
Microsoft.CodeAnalysis.EmbeddedAttribute
проверяется на этапе декларации
Введено в Visual Studio 2022 версии 17.13
Теперь компилятор проверяет структуру Microsoft.CodeAnalysis.EmbeddedAttribute
при объявлении в исходном коде. Ранее компилятор разрешал задаваемые пользователем объявления этого атрибута, но только в тех случаях, когда ему не нужно было генерировать его самостоятельно. Теперь мы проверяем, что:
- Он должен быть внутренним
- Это должен быть класс
- Он должен быть запечатан
- Он должен быть нестатичным
- Он должен иметь внутренний или общедоступный конструктор без параметров
- Он должен наследоваться от System.Attribute.
- Это должно быть допустимо в любом объявлении типа (класс, структура, интерфейс, перечисление или делегат)
namespace Microsoft.CodeAnalysis;
// Previously, sometimes allowed. Now, CS9271
public class EmbeddedAttribute : Attribute {}
Выражение field
в акцессоре свойства ссылается на синтезированное поле поддержки
Введено в Visual Studio 2022 версии 17.12
Выражение field
, когда используется в аксессоре свойства, ссылается на синтезированное поддерживающее поле для свойства.
Предупреждение CS9258 сообщается, когда идентификатор привязан к другому символу с языковой версией 13 или более ранней.
Чтобы избежать создания синтезированного резервного поля, а также для ссылки на существующий член, используйте вместо этого значение "this.field" или "@field".
Кроме того, переименуйте существующий элемент и ссылку на него, чтобы избежать конфликта с field
.
class MyClass
{
private int field = 0;
public object Property
{
get
{
// warning CS9258: The 'field' keyword binds to a synthesized backing field for the property.
// To avoid generating a synthesized backing field, and to refer to the existing member,
// use 'this.field' or '@field' instead.
return field;
}
}
}
Переменная с именем field
запрещена в аксессоре свойства
введена в Visual Studio 2022 версии 17.14
Выражение field
, когда используется в аксессоре свойства, ссылается на автоматически создаваемое вспомогательное поле для свойства.
Ошибка CS9272 сообщается, когда локальная переменная или параметр вложенной функции с именем field
объявлены в аксессоре свойства.
Чтобы избежать ошибки, переименуйте переменную или используйте @field
в объявлении.
class MyClass
{
public object Property
{
get
{
// error CS9272: 'field' is a keyword within a property accessor.
// Rename the variable or use the identifier '@field' instead.
int field = 0;
return @field;
}
}
}
Типы record
и record struct
не могут содержать членов указательного типа, даже если они предоставляют собственные реализации Equals.
введена в Visual Studio 2022 версии 17.14
Спецификация для типов record class
и record struct
указывает, что любые типы указателей не допускаются в качестве полей экземпляра.
Однако это не было применено правильно, когда тип record class
или record struct
определил собственную реализацию Equals
.
Компилятор теперь неправильно запрещает это.
unsafe record struct R(
int* P // Previously fine, now CS8908
)
{
public bool Equals(R other) => true;
}
Для создания исполняемых файлов, содержащих только метаданные, требуется наличие точки входа
введена в Visual Studio 2022 версии 17.14
Ранее точка входа непреднамеренно оставалась неустановленной при создании исполняемых файлов в режиме только для метаданных (также известных как ссылочные сборки). Это исправлено, но это также означает, что недостающая точка входа является ошибкой компиляции:
// previously successful, now fails:
CSharpCompilation.Create("test").Emit(new MemoryStream(),
options: EmitOptions.Default.WithEmitMetadataOnly(true))
CSharpCompilation.Create("test",
// workaround - mark as DLL instead of EXE (the default):
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
.Emit(new MemoryStream(),
options: EmitOptions.Default.WithEmitMetadataOnly(true))
Аналогично это можно наблюдать при использовании аргумента командной строки /refonly
или свойства MSBuild ProduceOnlyReferenceAssembly
.
partial
не может быть возвращаемым типом методов
введена в Visual Studio 2022 версии 17.14
Возможность языка частичных событий и конструкторов позволяет использовать модификатор partial
в большем количестве мест и поэтому не может быть возвращаемым типом, если не будет экранирован:
class C
{
partial F() => new partial(); // previously worked
@partial F() => new partial(); // workaround
}
class partial { }
extension
рассматривается как контекстное ключевое слово
Представлено в Visual Studio 2022 версии 17.14. Начиная с C# 14 extension
ключевое слово служит специальной целью для обозначения контейнеров расширений.
Это изменяет способ интерпретации определенных конструкций кода компилятором.
Если вам нужно использовать «расширение» в качестве идентификатора, а не ключевого слова, экранируйте его с помощью префикса @
: @extension
. Это указывает компилятору рассматривать его как обычный идентификатор вместо ключевого слова.
Компилятор анализирует это как контейнер расширения, а не конструктор.
class @extension
{
extension(object o) { } // parsed as an extension container
}
Компилятор не сможет проанализировать этот метод как метод с возвращаемым типом extension
.
class @extension
{
extension M() { } // will not compile
}
Представлено в Visual Studio 2022 версии 17.15. Идентификатор расширения может не использоваться в качестве имени типа, поэтому следующее не будет компилироваться:
using extension = ...; // alias may not be named "extension"
class extension { } // type may not be named "extension"
class C<extension> { } // type parameter may not be named "extension"
Roslyn breaking changes