Problemas de tipos anuláveis do C # 9
Considere o seguinte código (visualização do VS 16.8.0 Preview 2.1 C # 9.0):
#nullable enable
using System.Collections.Generic;
class Archive<T> where T : notnull
{
readonly Dictionary<string, T> Dict = new();
public T? GetAt(string key)
{
return Dict.TryGetValue(key, out var value) ? value : default;
}
}
class Manager
{
public int Age { get; set; }
}
class Main34
{
long F3()
{
Archive<long> a = new();
var johnAge = a.GetAt("john");
if (johnAge is null) return -1; // Error CS0037 Cannot convert null to 'long' because it is a non - nullable value type
return johnAge;
}
long F4()
{
Archive<Manager> a = new();
var johnAge = a.GetAt("john");
//if (johnAge is null) return -1;
return johnAge.Age; // Correct ! warning "Derefrencing of a possibly null reference" will be removed if line above unremarked
}
}
Estou tendo dificuldade em entender / resolver os erros na F3, Parece que o compilador acha johnAge há longnão long?(como eu verificada por pairando sobre ele em VS), apesar do retorno de Archive<T>.GetAtserT?
Existe uma maneira de ter um arquivo genérico que fará o que eu quero (um método GetAt que retorna Nullable mesmo quando T é um tipo básico não anulável, ou seja, longo)?
Respostas
Fundamentalmente, isso se resume a tipos de valor anuláveis e tipos de referência anuláveis sendo muito, muito diferentes. O CLR está ciente dos tipos de valor anuláveis, mas no que diz respeito ao CLR, os tipos de referência anuláveis são apenas "o tipo de referência normal, com um atributo informando ao compilador se ele deve ou não ser considerado anulável".
Quando Ttem a notnullrestrição, o tipo T?apenas compila Tno IL. Tem que - não pode compilar Nullable<T>, porque as Nullable<T>restrições Tsão um tipo de valor.
Assim, para um Archive<long>, o GetAtmétodo retornará 0D se a chave não for encontrada no dicionário - que não vai (e não pode) retornar o valor nulo de um Nullable<long>, que é o seu código em F3é efetivamente esperando.
Todo o recurso de "tipos de referência anuláveis" sofre por ser uma tentativa de adicionar um "verniz" de consciência anulável sobre um sistema de tipos que fundamentalmente não o possui. Tenho certeza de que se um novo tempo de execução e linguagem estivessem sendo projetados juntos do zero agora, ele tentaria unificar isso mais de perto. Do jeito que está, acredito que o recurso ainda tem muito valor - mas definitivamente torna as coisas realmente complicadas quando se trata de genéricos.