Como verificar se um object é anulável?

Como faço para verificar se um determinado object é anulável, em outras palavras, como implementar o seguinte método …

bool IsNullableValueType(object o) { ... } 

EDIT: Eu estou procurando por tipos de valor anulável. Eu não tenho tipos de ref em mente.

 //Note: This is just a sample. The code has been simplified //to fit in a post. public class BoolContainer { bool? myBool = true; } var bc = new BoolContainer(); const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ; object obj; object o = (object)bc; foreach (var fieldInfo in o.GetType().GetFields(bindingFlags)) { obj = (object)fieldInfo.GetValue(o); } 

obj agora se refere a um object do tipo bool ( System.Boolean ) com valor igual a true . O que eu realmente queria era um object do tipo Nullable

Então, agora, como um trabalho em volta, decidi verificar se o é anulável e criar um wrapper anulável em torno de obj.

Existem dois tipos de anulável – Nullable e tipo de referência.

Jon corrigiu-me que é difícil obter o tipo se estiver em checkbox, mas você pode com genéricos: – então como abaixo. Isso está realmente testando o tipo T , mas usando o parâmetro obj puramente para inferência de tipo genérico (para facilitar a chamada) – ele funcionaria quase identicamente sem o obj param, no entanto.

 static bool IsNullable(T obj) { if (obj == null) return true; // obvious Type type = typeof(T); if (!type.IsValueType) return true; // ref-type if (Nullable.GetUnderlyingType(type) != null) return true; // Nullable return false; // value-type } 

Mas isso não funcionará tão bem se você já tiver encaixotado o valor em uma variável de object.

Existe uma solução muito simples usando sobrecargas de methods

http://deanchalk.com/is-it-nullable/

excerto:

 public static class ValueTypeHelper { public static bool IsNullable(T t) { return false; } public static bool IsNullable(T? t) where T : struct { return true; } } 

então

 static void Main(string[] args) { int a = 123; int? b = null; object c = new object(); object d = null; int? e = 456; var f = (int?)789; bool result1 = ValueTypeHelper.IsNullable(a); // false bool result2 = ValueTypeHelper.IsNullable(b); // true bool result3 = ValueTypeHelper.IsNullable(c); // false bool result4 = ValueTypeHelper.IsNullable(d); // false bool result5 = ValueTypeHelper.IsNullable(e); // true bool result6 = ValueTypeHelper.IsNullable(f); // true 

A questão de “Como verificar se um tipo é anulável?” é realmente “Como verificar se um tipo é Nullable<> ?”, que pode ser generalizado para “Como verificar se um tipo é um tipo construído de algum tipo genérico?”, para que ele não responda apenas à pergunta “É Nullable um Nullable<> ? “, mas também” Is List a List<> ? “.

A maior parte da solução fornecida usa o método Nullable.GetUnderlyingType() , que obviamente funcionará apenas com o caso de Nullable<> . Eu não vi a solução reflexiva geral que funcionará com qualquer tipo genérico, então decidi adicioná-la aqui para a posteridade, mesmo que essa pergunta já tenha sido respondida há muito tempo.

Para verificar se um tipo é alguma forma de Nullable<> usando reflection, primeiro você precisa converter seu tipo genérico construído, por exemplo Nullable , na definição de tipo genérico, Nullable<> . Você pode fazer isso usando o método GetGenericTypeDefinition() da class Type . Você pode então comparar o tipo resultante com Nullable<> :

 Type typeToTest = typeof(Nullable); bool isNullable = typeToTest.GetGenericTypeDefinition() == typeof(Nullable<>); // isNullable == true 

O mesmo pode ser aplicado a qualquer tipo genérico:

 Type typeToTest = typeof(List); bool isList = typeToTest.GetGenericTypeDefinition() == typeof(List<>); // isList == true 

Vários tipos podem parecer iguais, mas um número diferente de argumentos de tipo significa que é um tipo completamente diferente.

 Type typeToTest = typeof(Action); bool isAction1 = typeToTest.GetGenericTypeDefinition() == typeof(Action<>); bool isAction2 = typeToTest.GetGenericTypeDefinition() == typeof(Action< ,>); bool isAction3 = typeToTest.GetGenericTypeDefinition() == typeof(Action< ,,>); // isAction1 == false // isAction2 == true // isAction3 == false 

Como o object Type é instanciado uma vez por tipo, você pode verificar a igualdade de referência entre eles. Então, se você quiser verificar se dois objects são da mesma definição de tipo genérico, você pode escrever:

 var listOfInts = new List(); var listOfStrings = new List(); bool areSameGenericType = listOfInts.GetType().GetGenericTypeDefinition() == listOfStrings.GetType().GetGenericTypeDefinition(); // areSameGenericType == true 

Se você quiser verificar se um object é anulável, em vez de um Type , então você pode usar a técnica acima junto com a solução de Marc Gravell para criar um método bastante simples:

 static bool IsNullable(T obj) { if (!typeof(T).IsGenericType) return false; return typeof(T).GetGenericTypeDefinition() == typeof(Nullable<>); } 

Bem, você poderia usar:

 return !(o is ValueType); 

… mas um object em si não é anulável ou não – um tipo é. Como você planejava usar isso?

Isso funciona para mim e parece simples:

 static bool IsNullable(T obj) { return default(T) == null; } 

A maneira mais simples de descobrir é:

 public bool IsNullable(object obj) { Type t = obj.GetType(); return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>); } 

Há dois problemas aqui: 1) testar para ver se um Type é anulável; e 2) testar para ver se um object representa um tipo anulável.

Para o problema 1 (testando um Type), aqui está uma solução que usei em meus próprios sistemas: TypeIsNullable-check solution

Para o problema 2 (testar um object), a solução de Dean Chalk acima funciona para tipos de valor, mas não funciona para tipos de referência, pois o uso da sobrecarga sempre retorna falso. Como os tipos de referência são inerentemente anuláveis, o teste de um tipo de referência deve sempre retornar true. Por favor, veja a nota [About “nullability”] abaixo para uma explicação dessas semânticas. Assim, aqui está minha modificação para a abordagem de Dean:

  public static bool IsObjectNullable(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? 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 para o 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 eu modifiquei a abordagem de Dean em IsObjectNullable (T t) é que sua abordagem original sempre retornou false para um tipo de referência. Como um método como IsObjectNullable deve ser capaz de manipular valores de 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 deve sempre retornar true.

Os dois methods acima podem ser substituídos pelo seguinte método único e obter a mesma saída:

  public static bool IsObjectNullable(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 é usado. É preciso muito mais tempo de processador para executar a última linha desse método único do que permitir que o compilador escolha a segunda sobrecarga de método mostrada anteriormente quando um parâmetro Nullable é usado na chamada IsObjectNullable. Portanto, a solução ideal é usar a abordagem de dois methods ilustrada aqui.

CAVEAT: Esse método funciona de forma confiável somente se chamado usando a referência de object original ou uma cópia exata, conforme mostrado nos exemplos. No entanto, se um object anulável é colocado em checkbox para outro tipo (como object, etc.) em vez de permanecer em sua forma original Nullable <>, esse método não funcionará de forma confiável. Se o código que chama esse método não estiver usando a referência de objeto original, sem caixa ou uma cópia exata, não poderá determinar com confiabilidade a capacidade de anulação do objeto usando esse método.

Na maioria dos cenários de codificação, para determinar a nulidade deve-se confiar em testar o tipo do object original, não sua referência (por exemplo, o código deve ter access ao tipo original do object para determinar a nulidade). Nestes casos mais comuns, IsTypeNullable (ver link) é um método confiável para determinar a nulidade.

PS – Sobre “anulabilidade”

Eu deveria repetir uma declaração sobre a anulabilidade que eu fiz em um post separado, que se aplica diretamente ao endereçamento correto deste tópico. Ou seja, acredito que o foco da discussão aqui não deveria ser como verificar se um object é um tipo Nullable genérico, mas sim se é possível atribuir um valor null a um object de seu tipo. Em outras palavras, acho que devemos determinar se um tipo de object é anulável, e não se é anulável. A diferença está na semântica, ou seja, as razões práticas para determinar a nulidade, que geralmente é tudo o que importa.

Em um sistema que usa objects 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 object ou se o object pode conter um nulo. Realizar essas operações em tipos não anuláveis ​​provavelmente produzirá erros, geralmente exceções, que são muito caros em termos de desempenho e requisitos de codificação. Para adotar a abordagem altamente preferida de evitar proativamente esses problemas, é necessário determinar se um object de um Tipo arbitrário é capaz de conter um nulo; isto é, se é geralmente ‘anulável’.

Em um sentido muito prático e típico, a nulidade em termos .NET não implica necessariamente que o tipo de um object seja uma forma de Nullable. Em muitos casos, de fato, os objects têm tipos de referência, podem conter um valor nulo e, portanto, são todos anuláveis; nenhum deles tem um tipo Nullable. Portanto, para fins práticos na maioria dos cenários, o teste deve ser feito para o conceito geral de anulação, versus o conceito dependente de implementação de Nullable. Portanto, não devemos nos concentrar apenas no tipo .NET Nullable, mas sim incorporar nossa compreensão de seus requisitos e comportamento no processo de enfocar o conceito geral e prático de nulidade.

Tenha cuidado, quando encheckboxndo um tipo anulável ( Nullable ou int? Por exemplo):

 int? nullValue = null; object boxedNullValue = (object)nullValue; Debug.Assert(boxedNullValue == null); int? value = 10; object boxedValue = (object)value; Debug.Assert( boxedValue.GetType() == typeof(int)) 

Ele se torna um verdadeiro tipo de referência, então você perde o fato de ser anulável.

A solução mais simples que eu criei foi implementar a solução da Microsoft ( Como: Identificar um tipo anulável (guia de programação C #) ) como um método de extensão:

 public static bool IsNullable(this Type type) { return type.IsGenericType && type.GetGenericTypeDefinition() != typeof(Nullable<>); } 

Isso pode ser chamado assim:

 bool isNullable = typeof(int).IsNullable(); 

Isso também parece uma maneira lógica para acessar IsNullable() porque ele se encheckbox com todos os outros methods IsXxxx() da class Type .

Talvez um pouco fora do tópico, mas ainda alguma informação interessante. Eu acho um monte de pessoas que usam Nullable.GetUnderlyingType() != null para identidade se um tipo é anulável. Isso obviamente funciona, mas a Microsoft aconselha o seguinte type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) (consulte http://msdn.microsoft.com/en-us/library/ms366789.aspx ).

Eu olhei para isso de um lado do desempenho da visão. A conclusão do teste (um milhão de tentativas) abaixo é que, quando um tipo é anulável, a opção Microsoft oferece o melhor desempenho.

Nullable.GetUnderlyingType (): 1335ms (3 vezes mais lento)

GetGenericTypeDefinition () == typeof (Nullable <>): 500ms

Eu sei que estamos falando de uma pequena quantidade de tempo, mas todo mundo gosta de ajustar os milissegundos :-)! Então, se você é chefe quer que você reduza alguns milissegundos, então este é seu salvador …

 /// Method for testing the performance of several options to determine if a type is nullable [TestMethod] public void IdentityNullablePerformanceTest() { int attempts = 1000000; Type nullableType = typeof(Nullable); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int attemptIndex = 0; attemptIndex < attempts; attemptIndex++) { Assert.IsTrue(Nullable.GetUnderlyingType(nullableType) != null, "Expected to be a nullable"); } Console.WriteLine("Nullable.GetUnderlyingType(): {0} ms", stopwatch.ElapsedMilliseconds); stopwatch.Restart(); for (int attemptIndex = 0; attemptIndex < attempts; attemptIndex++) { Assert.IsTrue(nullableType.IsGenericType && nullableType.GetGenericTypeDefinition() == typeof(Nullable<>), "Expected to be a nullable"); } Console.WriteLine("GetGenericTypeDefinition() == typeof(Nullable<>): {0} ms", stopwatch.ElapsedMilliseconds); stopwatch.Stop(); } 

Esta versão:

  • os resultados do cache são mais rápidos
  • não requer variables ​​desnecessárias, como Method (T obj)
  • NÃO É COMPLICADO :),
  • apenas class genérica estática, que tem campos calculados uma vez

:

 public static class IsNullable { private static readonly Type type = typeof(T); private static readonly bool is_nullable = type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); public static bool Result { get { return is_nullable; } } } bool is_nullable = IsNullable.Result; 

Aqui está o que eu criei, como tudo o mais parecia falhar – pelo menos no CLP – Portable Class Library / .NET Core com> = C # 6

Solução: Estenda methods estáticos para qualquer Tipo T e Nullable e use o fato de que o método de extensão estática, correspondente ao tipo subjacente, será chamado e terá precedência sobre o método de extensão T genérico.

Para T :

 public static partial class ObjectExtension { public static bool IsNullable(this T self) { return false; } } 

e para Nullable

 public static partial class NullableExtension { public static bool IsNullable(this Nullable self) where T : struct { return true; } } 

Usando Reflection e type.IsGenericType … não funcionou no meu conjunto atual de .NET Runtimes. Nem a documentação da MSDN ajudou.

if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) {…}

Em parte porque a Reflection API foi alterada de forma bastante significativa no .NET Core.

uma maneira simples de fazer isso:

  public static bool IsNullable(this Type type) { if (type.IsValueType) return Activator.CreateInstance(type) == null; return true; } 

estes são os meus testes de unidade e todos passaram

  IsNullable_String_ShouldReturn_True IsNullable_Boolean_ShouldReturn_False IsNullable_Enum_ShouldReturn_Fasle IsNullable_Nullable_ShouldReturn_True IsNullable_Class_ShouldReturn_True IsNullable_Decimal_ShouldReturn_False IsNullable_Byte_ShouldReturn_False IsNullable_KeyValuePair_ShouldReturn_False 

testes unitários reais

  [TestMethod] public void IsNullable_String_ShouldReturn_True() { var typ = typeof(string); var result = typ.IsNullable(); Assert.IsTrue(result); } [TestMethod] public void IsNullable_Boolean_ShouldReturn_False() { var typ = typeof(bool); var result = typ.IsNullable(); Assert.IsFalse(result); } [TestMethod] public void IsNullable_Enum_ShouldReturn_Fasle() { var typ = typeof(System.GenericUriParserOptions); var result = typ.IsNullable(); Assert.IsFalse(result); } [TestMethod] public void IsNullable_Nullable_ShouldReturn_True() { var typ = typeof(Nullable); var result = typ.IsNullable(); Assert.IsTrue(result); } [TestMethod] public void IsNullable_Class_ShouldReturn_True() { var typ = typeof(TestPerson); var result = typ.IsNullable(); Assert.IsTrue(result); } [TestMethod] public void IsNullable_Decimal_ShouldReturn_False() { var typ = typeof(decimal); var result = typ.IsNullable(); Assert.IsFalse(result); } [TestMethod] public void IsNullable_Byte_ShouldReturn_False() { var typ = typeof(byte); var result = typ.IsNullable(); Assert.IsFalse(result); } [TestMethod] public void IsNullable_KeyValuePair_ShouldReturn_False() { var typ = typeof(KeyValuePair); var result = typ.IsNullable(); Assert.IsFalse(result); }