TG Telegram Group & Channel
.NET Разработчик | United States America (US)
Create: Update:

День 2221. #ЗаметкиНаПолях
Как Компилятор Выводит Тип из default?

Ключевое слово default — мощный инструмент в C#. Для ссылочных типов он возвращает null, а для типов значений (структур) — обнулённое значение. Интересно, что default(T) и new T() могут давать разные результаты для структур. Изначально C# требовал default(T), но теперь вы можете просто использовать default, когда тип может быть выведен компилятором. Но как компилятор выводит тип? И всегда ли вы можете доверять, что он сделает это правильно?

Давайте рассмотрим два простых случая:

// Выведение типа из левой части 
int foo = default;

// Выведение типа из типа параметра
Foo(default);
void Foo(int bar) => throw null;


Предыдущие случаи довольно очевидны, но что будет с выражением switch?
var sample = new byte[0];
ReadOnlyMemory<byte> foo = sample switch
{
byte[] value => value,
_ => default
};

Вы можете ожидать, что default будет default(ReadOnlyMemory<byte>), так как это видимый тип в левой части. Однако на самом деле это будет default(byte[]). Компилятор выводит тип из ветвей выражения switch. В этом случае компилятор определит наилучший общий тип для всех случаев, которым в данном случае является byte[]. Следовательно, типом default будет byte[].

В предыдущем примере результат foo не поменяется, если вы используете default(byte[]) или default(ReadOnlyMemory<byte>). Но это может иметь некоторые последствия. Давайте изменим тип с ReadOnlyMemory<byte> на ReadOnlyMemory<byte>? (обнуляемый).
var sample = new object();
ReadOnlyMemory<byte>? foo = sample switch
{
byte[] value => value,
_ => default
};

В этом примере типом default всё ещё будет default(byte[]). Поэтому выражение switch вернёт default(byte[]), что является null. Однако, затем значение будет преобразовано в ReadOnlyMemory<byte>?. А у ReadOnlyMemory<T> есть оператор неявного преобразования из массива:
// из исходного кода .NET 9
public static implicit operator
ReadOnlyMemory<T>(T[]? array)
=> new ReadOnlyMemory<T>(array);

В свою очередь, конструктор ReadOnlyMemory<T>, принимающий обнуляемый массив, создаёт пустой ReadOnlyMemory<T>, если получает null:
// из исходного кода .NET 9
public ReadOnlyMemory(T[]? array)
{
if (array == null)
{
this = default;
return; // returns default
}

}

Таким образом, на выходе мы получим пустой ReadOnlyMemory<byte>, и foo.HasValue будет true!

А если мы явно укажем обнуляемый тип в default:
object sample = new();

ReadOnlyMemory<byte>? foo = sample switch
{
byte[] value => value,
_ => default(ReadOnlyMemory<byte>?)
};

Тогда закономерно получим null в выражении switch, и foo.HasValue будет false.

Источник: https://www.meziantou.net/how-does-the-compiler-infer-the-type-of-default.htm

День 2221. #ЗаметкиНаПолях
Как Компилятор Выводит Тип из default?

Ключевое слово default — мощный инструмент в C#. Для ссылочных типов он возвращает null, а для типов значений (структур) — обнулённое значение. Интересно, что default(T) и new T() могут давать разные результаты для структур. Изначально C# требовал default(T), но теперь вы можете просто использовать default, когда тип может быть выведен компилятором. Но как компилятор выводит тип? И всегда ли вы можете доверять, что он сделает это правильно?

Давайте рассмотрим два простых случая:
// Выведение типа из левой части 
int foo = default;

// Выведение типа из типа параметра
Foo(default);
void Foo(int bar) => throw null;


Предыдущие случаи довольно очевидны, но что будет с выражением switch?
var sample = new byte[0];
ReadOnlyMemory<byte> foo = sample switch
{
byte[] value => value,
_ => default
};

Вы можете ожидать, что default будет default(ReadOnlyMemory<byte>), так как это видимый тип в левой части. Однако на самом деле это будет default(byte[]). Компилятор выводит тип из ветвей выражения switch. В этом случае компилятор определит наилучший общий тип для всех случаев, которым в данном случае является byte[]. Следовательно, типом default будет byte[].

В предыдущем примере результат foo не поменяется, если вы используете default(byte[]) или default(ReadOnlyMemory<byte>). Но это может иметь некоторые последствия. Давайте изменим тип с ReadOnlyMemory<byte> на ReadOnlyMemory<byte>? (обнуляемый).
var sample = new object();
ReadOnlyMemory<byte>? foo = sample switch
{
byte[] value => value,
_ => default
};

В этом примере типом default всё ещё будет default(byte[]). Поэтому выражение switch вернёт default(byte[]), что является null. Однако, затем значение будет преобразовано в ReadOnlyMemory<byte>?. А у ReadOnlyMemory<T> есть оператор неявного преобразования из массива:
// из исходного кода .NET 9
public static implicit operator
ReadOnlyMemory<T>(T[]? array)
=> new ReadOnlyMemory<T>(array);

В свою очередь, конструктор ReadOnlyMemory<T>, принимающий обнуляемый массив, создаёт пустой ReadOnlyMemory<T>, если получает null:
// из исходного кода .NET 9
public ReadOnlyMemory(T[]? array)
{
if (array == null)
{
this = default;
return; // returns default
}

}

Таким образом, на выходе мы получим пустой ReadOnlyMemory<byte>, и foo.HasValue будет true!

А если мы явно укажем обнуляемый тип в default:
object sample = new();

ReadOnlyMemory<byte>? foo = sample switch
{
byte[] value => value,
_ => default(ReadOnlyMemory<byte>?)
};

Тогда закономерно получим null в выражении switch, и foo.HasValue будет false.

Источник: https://www.meziantou.net/how-does-the-compiler-infer-the-type-of-default.htm
4👍18


>>Click here to continue<<

.NET Разработчик




Share with your best friend
VIEW MORE

United States America Popular Telegram Group (US)