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?

edosoft
fonte
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
Jon Skeet
fonte
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);
Ricardo Villamil
fonte
Este link: msdn.microsoft.com/en-us/library/xwth0h0d(VS.80).aspx deve explicar o porquê.
Harper Shelby
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.

TheSoftwareJedi
fonte
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()
Mín.
fonte
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);
user725388
fonte
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;
Jaydeep Shil
fonte
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.

BFree
fonte
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;
}
devi
fonte
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".

Mitchel Sellers
fonte
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);
}
Lucas
fonte
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
}
Jeson Martajaya
fonte
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);

LCIII
fonte
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


}
Mertuarez
fonte