Como posso retornar NULL de um método genérico em c #?


546

Eu tenho um método genérico com esse código (fictício) (sim, eu sei que IList tem predicados, mas meu código não está usando IList, mas alguma outra coleção, de qualquer maneira isso é irrelevante para a pergunta ...)

static T FindThing<T>(IList collection, int id) where T : IThing, new()
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return thing;
    }
    return null;  // ERROR: Cannot convert null to type parameter 'T' because it could be a value type. Consider using 'default(T)' instead.
}

Isso me dá um erro de compilação

"Não é possível converter nulo no parâmetro de tipo 'T' porque pode ser um tipo de valor. Considere usar 'padrão (T)'."

Posso evitar esse erro?


Os tipos de referência anuláveis ​​(em C # 8) seriam a melhor solução para isso agora? docs.microsoft.com/en-us/dotnet/csharp/nullable-references Retornando nullindependentemente de se Té Objectou intou char.
Alexander - Restabelecer Monica

Respostas:


969

Duas opções:

  • Return, o default(T)que significa que você retornará nullse T for um tipo de referência (ou um tipo de valor anulável), 0for int, '\0'for char, etc. ( Tabela de valores padrão (Referência de C #) )
  • Restrinja T a ser um tipo de referência com a where T : classrestrição e retorne nullnormalmente

3
E se meu tipo de retorno for um enum e não uma classe? Não consigo especificar T: enum :(
Justin

1
No .NET, um enum é um invólucro muito fino (e com vazamento) em torno de um tipo inteiro. A convenção é usar zero para o seu valor de enumeração "padrão".
Mike Chamberlain

27
Acho que o problema é que, se você estiver usando esse método genérico para dizer, converta um objeto Database de DbNull em Int e retorne default (T) onde T é int, retornará 0. Se esse número for realmente significativo, você passaria dados inválidos nos casos em que esse campo fosse nulo. Ou um exemplo melhor seria um DateTime. Se o campo fosse algo como "DateClosed" e fosse retornado como nulo porque a conta ainda estivesse aberta, na verdade, o padrão (DateTime) seria 1/1/0000, o que implica que a conta foi fechada antes da invenção dos computadores.
Sinaesthetic

21
@ Sinestésico: Então você converteria para Nullable<int>ou em Nullable<DateTime>vez disso. Se você usa um tipo não nulo e precisa representar um valor nulo, está apenas pedindo problemas.
Jon Skeet

1
Eu concordo, eu só queria trazer à tona. Eu acho que o que tenho feito é mais como MyMethod <T> (); supor que é um tipo não anulável e MyMethod <T?> (); supor que é um tipo anulável. Portanto, em meus cenários, eu poderia usar uma variável temp para capturar um nulo e partir daí.
Sinaesthetic

84
return default(T);


1
Droga, eu teria economizado muito tempo se soubesse dessa palavra-chave - obrigado Ricardo!
Ana Betts

1
Estou surpreso que isso não tenha recebido mais votos, já que a palavra-chave 'padrão' é uma solução mais abrangente, permitindo o uso de tipos sem referência em conjunto com tipos e estruturas numéricos. Embora a resposta aceita resolva o problema (e realmente seja útil), ela responde melhor como restringir o tipo de retorno aos tipos de referência / nulos.
Steve Jackson

33

Você pode apenas ajustar suas restrições:

where T : class

Então, retornar nulo é permitido.


Obrigado. Não posso escolher 2 respostas como a solução aceita, por isso escolhi John Skeet porque a resposta dele tem duas soluções.
edosoft

@Migol depende de suas necessidades. Talvez o projeto deles exija IDisposable. Sim, na maioria das vezes não precisa ser. System.Stringnão implementa IDisposable, por exemplo. O respondente deveria ter esclarecido isso, mas isso não faz a resposta errada. :)
ahwm 30/08/19

@Migol Eu não tenho idéia por que eu tinha IDisposable lá. Removido.
TheSoftwareJedi

13

Adicione a restrição de classe como a primeira restrição ao seu tipo genérico.

static T FindThing<T>(IList collection, int id) where T : class, IThing, new()

Obrigado. Não posso escolher 2 respostas como a solução aceita, por isso escolhi John Skeet porque a resposta dele tem duas soluções.
edosoft

7
  1. Se você tiver um objeto, precisará digitar

    return (T)(object)(employee);
  2. se você precisar retornar nulo.

    return default(T);

Oi user725388, verifique a primeira opção
Jogi Joseph George

7

Abaixo estão as duas opções que você pode usar

return default(T);

ou

where T : class, IThing
 return null;

6

Sua outra opção seria adicionar isso ao final da sua declaração:

    where T : class
    where T: IList

Dessa forma, você poderá retornar nulo.


Se ambas as restrições forem do mesmo tipo, você menciona o tipo uma vez e usa uma vírgula, como where T : class, IList. Se você tiver restrições para tipos diferentes, repita o token where, como em where TFoo : class where TBar : IList.
Jeppe Stig Nielsen

3

solução de TheSoftwareJedi funciona,

Também é possível arquivá-lo usando alguns tipos de valor e valores nulos:

static T? FindThing<T>(IList collection, int id) where T : struct, IThing
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return thing;
    }
    return null;
}

1

Siga a recomendação do erro ... e use user default(T)ou new T.

Você precisará adicionar uma comparação ao seu código para garantir que seja uma correspondência válida se você seguir esse caminho.

Caso contrário, considere potencialmente um parâmetro de saída para "correspondência encontrada".


1

Aqui está um exemplo de trabalho para valores de retorno Nullable Enum:

public static TEnum? ParseOptional<TEnum>(this string value) where TEnum : struct
{
    return value == null ? (TEnum?)null : (TEnum) Enum.Parse(typeof(TEnum), value);
}

Desde o C # 7.3 (maio de 2018), você pode melhorar a restrição para where TEnum : struct, Enum. Isso garante que um chamador não forneça acidentalmente um tipo de valor que não seja um enum (como um intou a DateTime).
Jeppe Stig Nielsen

0

Outra alternativa para as 2 respostas apresentadas acima. Se você alterar o tipo de retorno para object, poderá retornar e null, ao mesmo tempo, converter o retorno não nulo.

static object FindThing<T>(IList collection, int id)
{
    foreach T thing in collecion
    {
        if (thing.Id == id)
            return (T) thing;
    }
    return null;  // allowed now
}

Desvantagem: isso exigiria que o chamador do método convertesse o objeto retornado (em caso não nulo), o que implica em boxe -> menos desempenho. Estou certo?
Csharpest 19/02

0

Por uma questão de integridade, é bom saber que você também pode fazer isso:

return default;

Retorna o mesmo que return default(T);


0

Para mim, funciona como está. Onde exatamente está o problema?

public static T FindThing<T>(this IList collection, int id) where T : IThing, new()
{
    foreach (T thing in collection)
    {
        if (thing.Id == id)
            return thing;
        }
    }

    return null; //work
    return (T)null; //work
    return null as T; //work
    return default(T); //work


}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.