Existem dois problemas aqui: 1) teste para verificar se um Tipo é anulável; e 2) teste para verificar se um objeto representa um Tipo anulável.
Para a edição 1 (testando um tipo), aqui está uma solução que eu usei em meus próprios sistemas: solução de verificação TypeIsNullable
Para o problema 2 (testando um objeto), a solução de Dean Chalk acima funciona para tipos de valor, mas não para tipos de referência, pois o uso da sobrecarga <T> sempre retorna falso. Como os tipos de referência são inerentemente anuláveis, o teste de um tipo de referência sempre deve retornar verdadeiro. Por favor, veja a nota [Sobre "nulidade"] abaixo para obter uma explicação dessas semânticas. Assim, aqui está a minha modificação na abordagem de Dean:
public static bool IsObjectNullable<T>(T obj)
{
// If the parameter-Type is a reference type, or if the parameter is null, then the object is always nullable
if (!typeof(T).IsValueType || obj == null)
return true;
// Since the object passed is a ValueType, and it is not null, it cannot be a nullable object
return false;
}
public static bool IsObjectNullable<T>(T? obj) where T : struct
{
// Always return true, since the object-type passed is guaranteed by the compiler to always be nullable
return true;
}
E aqui está minha modificação no código de teste do cliente para a solução acima:
int a = 123;
int? b = null;
object c = new object();
object d = null;
int? e = 456;
var f = (int?)789;
string g = "something";
bool isnullable = IsObjectNullable(a); // false
isnullable = IsObjectNullable(b); // true
isnullable = IsObjectNullable(c); // true
isnullable = IsObjectNullable(d); // true
isnullable = IsObjectNullable(e); // true
isnullable = IsObjectNullable(f); // true
isnullable = IsObjectNullable(g); // true
A razão pela qual modifiquei a abordagem de Dean em IsObjectNullable <T> (T t) é que sua abordagem original sempre retornava falsa para um tipo de referência. Como um método como IsObjectNullable deve poder manipular valores do tipo de referência e como todos os tipos de referência são inerentemente anuláveis, se um tipo de referência ou um nulo for passado, o método sempre retornará true.
Os dois métodos acima podem ser substituídos pelo seguinte método único e obter a mesma saída:
public static bool IsObjectNullable<T>(T obj)
{
Type argType = typeof(T);
if (!argType.IsValueType || obj == null)
return true;
return argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(Nullable<>);
}
No entanto, o problema com essa última abordagem de método único é que o desempenho sofre quando um parâmetro Nullable <T> é usado. Leva muito mais tempo do processador para executar a última linha desse método único do que para permitir que o compilador escolha a segunda sobrecarga de método mostrada anteriormente quando um parâmetro do tipo Nullable <T> é usado na chamada IsObjectNullable. Portanto, a solução ideal é usar a abordagem de dois métodos ilustrada aqui.
CAVEAT: esse método funciona de maneira confiável apenas se for chamado usando a referência do objeto original ou uma cópia exata, conforme mostrado nos exemplos. No entanto, se um objeto anulável estiver encaixotado em outro Tipo (como objeto, etc.) em vez de permanecer em seu formulário Nullable <> original, esse método não funcionará de maneira confiável. Se o código que chama esse método não estiver usando a referência original do objeto sem caixa ou uma cópia exata, ele não poderá determinar com segurança a nulidade do objeto usando esse método.
Na maioria dos cenários de codificação, para determinar a nulidade, deve-se confiar no teste do Tipo do objeto original, não na sua referência (por exemplo, o código deve ter acesso ao Tipo original do objeto para determinar a nulidade). Nesses casos mais comuns, IsTypeNullable (consulte o link) é um método confiável para determinar a anulabilidade.
PS - Sobre "nulidade"
Devo repetir uma declaração sobre a nulidade que fiz em um post separado, que se aplica diretamente ao tratamento adequado deste tópico. Ou seja, acredito que o foco da discussão aqui não deve ser como verificar se um objeto é do tipo Nullable genérico, mas se é possível atribuir um valor nulo a um objeto do seu tipo. Em outras palavras, acho que deveríamos determinar se um tipo de objeto é anulável, não se é anulável. A diferença está na semântica, a saber, as razões práticas para determinar a nulidade, que geralmente é tudo o que importa.
Em um sistema que usa objetos com tipos possivelmente desconhecidos até o tempo de execução (serviços da Web, chamadas remotas, bancos de dados, feeds etc.), um requisito comum é determinar se um nulo pode ser atribuído ao objeto ou se o objeto pode conter um nulo. A execução de tais operações em tipos não anuláveis provavelmente produzirá erros, geralmente exceções, que são muito caros, tanto em termos de desempenho quanto de requisitos de codificação. Para adotar a abordagem altamente preferida de evitar proativamente esses problemas, é necessário determinar se um objeto de um Tipo arbitrário é capaz de conter um valor nulo; ou seja, se é geralmente 'anulável'.
Em um sentido muito prático e típico, a nulidade em termos do .NET não implica necessariamente que o Type de um objeto seja uma forma de Nullable. De fato, em muitos casos, os objetos têm tipos de referência, podem conter um valor nulo e, portanto, são todos anuláveis; nenhum deles tem um tipo nulo. Portanto, para propósitos práticos na maioria dos cenários, o teste deve ser realizado para o conceito geral de nulidade, versus o conceito de Nullable, dependente da implementação. Portanto, não devemos ficar concentrados apenas no tipo .NET Nullable, mas incorporar nosso entendimento de seus requisitos e comportamento no processo de foco no conceito geral e prático de nulidade.