FirstOrDefault: valor padrão diferente de null


142

Pelo que entendi, no Linq o método FirstOrDefault()pode retornar um Defaultvalor de algo diferente de nulo. O que ainda não descobri é que tipo de coisa diferente de nulo pode ser retornada por esse método (e semelhante) quando não há itens no resultado da consulta. Existe alguma maneira específica de configurar isso para que, se não houver valor para uma consulta específica, algum valor predefinido seja retornado como o valor padrão?


147
Em vez de YourCollection.FirstOrDefault(), você poderia usar YourCollection.DefaultIfEmpty(YourDefault).First()por exemplo.
Preguiça

6
Eu tenho procurado por algo como o comentário acima por um bom tempo, ajudou imensamente. Essa deve ser a resposta aceita.
Brandon

O comentário acima é a melhor resposta.
Tom Padilla

No meu caso, a resposta @sloth não funcionou quando o valor retornado é anulável e atribuído a um não anulável. Eu usei MyCollection.Last().GetValueOrDefault(0)para isso. Caso contrário, a resposta de @Jon Skeet abaixo está correta na IMO.
Jnr

Respostas:


46

Caso geral, não apenas para tipos de valor:

static class ExtensionsThatWillAppearOnEverything
{
    public static T IfDefaultGiveMe<T>(this T value, T alternate)
    {
        if (value.Equals(default(T))) return alternate;
        return value;
    }
}

var result = query.FirstOrDefault().IfDefaultGiveMe(otherDefaultValue);

Novamente, isto não pode realmente dizer se há foi nada na sua sequência, ou se o primeiro valor era o padrão.

Se você se importa com isso, pode fazer algo como

static class ExtensionsThatWillAppearOnIEnumerables
{
    public static T FirstOr<T>(this IEnumerable<T> source, T alternate)
    {
        foreach(T t in source)
            return t;
        return alternate;
    }
}

e use como

var result = query.FirstOr(otherDefaultValue);

embora, como o Sr. Steak ressalte, isso poderia ser feito da mesma maneira .DefaultIfEmpty(...).First().


Seus métodos genéricos precisam <T>em seus nomes, mas mais grave é que value == default(T)não funciona (porque quem sabe se Tpode ser comparado por igualdade?)
AakashM

Obrigado por apontar isso, @AakashM; Na verdade, eu tentei isso agora e acho que deve estar OK (embora eu não goste do boxe para tipos de valor).
Rawling

3
@Rawling Use EqualityComparer<T>.Default.Equals(value, default(T))para evitar o boxe e evitar uma exceção se o valor énull
Lukazoid

199

Pelo que entendi, no Linq o método FirstOrDefault () pode retornar um valor padrão de algo diferente de nulo.

Não. Ou melhor, ele sempre retorna o valor padrão para o tipo de elemento ... que é uma referência nula, o valor nulo de um tipo de valor nulo ou o valor natural "todos os zeros" para um tipo de valor não nulo.

Existe alguma maneira específica de configurar isso para que, se não houver valor para uma consulta específica, algum valor predefinido seja retornado como o valor padrão?

Para tipos de referência, você pode apenas usar:

var result = query.FirstOrDefault() ?? otherDefaultValue;

Obviamente, isso também fornecerá o "outro valor padrão" se o primeiro valor estiver presente, mas for uma referência nula ...


Eu sei que a pergunta pede um tipo de referência, mas sua solução não funciona quando elementos são do tipo valor int. Eu prefiro muito mais o uso de DefaultIfEmpty: src.Where(filter).DefaultIfEmpty(defaultValue).First(). Funciona para o tipo de valor e o tipo de referência.
KFL 16/02

@KFL: Para tipos de valores não anuláveis, eu provavelmente usaria isso também - mas é mais demorado para casos anuláveis.
Jon Skeet

Incrível controle sobre tipos de retorno ao padrão é nulo .. :)
Sundara Prabu

"Não. Ou melhor, ele sempre retorna o valor padrão para o tipo de elemento ..." - Isso foi feito para mim, na verdade ... pois eu também entendi mal o significado do nome da função assumindo que você poderia fornecer qualquer valor padrão quando necessário
Jesus Campon

Gostei muito dessa abordagem, mas alterei o "T alternate" por "Func <T> alternate" e depois "return alternate ();" Dessa forma, não gerarei o objeto extra, a menos que precise. Especialmente útil se a função é chamada várias vezes seguidas, o construtor é lento ou a instância alternativa do tipo consome muita memória.
Dan Violet Sagmiller

64

Você pode usar DefaultIfEmpty seguido por First :

T customDefault = ...;
IEnumerable<T> mySequence = ...;
mySequence.DefaultIfEmpty(customDefault).First();

Eu amo a idéia de DefaultIfEmpty- ele funciona com todas as APIs que precisam de um padrão a ser especificado: First(), Last(), etc. Como usuário, você não precisa se lembrar que APIs permitem especificar padrão que não fazer. Muito elegante!
KFL 16/02

Esta é muito a resposta do ninho.
Jesse Williams

19

A partir da documentação para FirstOrDefault

[Retorna] padrão (TSource) se a fonte estiver vazia;

Na documentação para o padrão (T) :

a palavra-chave padrão, que retornará nulo para tipos de referência e zero para tipos de valores numéricos. Para estruturas, ele retornará cada membro da estrutura inicializado como zero ou nulo, dependendo de serem tipos de valor ou referência. Para tipos de valores anuláveis, o padrão retorna um System.Nullable, que é inicializado como qualquer estrutura.

Portanto, o valor padrão pode ser nulo ou 0, dependendo se o tipo é uma referência ou um tipo de valor, mas você não pode controlar o comportamento padrão.


6

Copiado do comentário por @sloth

Em vez de YourCollection.FirstOrDefault(), você poderia usar YourCollection.DefaultIfEmpty(YourDefault).First()por exemplo.

Exemplo:

var viewModel = new CustomerDetailsViewModel
    {
            MainResidenceAddressSection = (MainResidenceAddressSection)addresses.DefaultIfEmpty(new MainResidenceAddressSection()).FirstOrDefault( o => o is MainResidenceAddressSection),
            RiskAddressSection = addresses.DefaultIfEmpty(new RiskAddressSection()).FirstOrDefault(o => !(o is MainResidenceAddressSection)),
    };

2
Observe que DefaultIfEmptyretorna o padrão SE a coleção estiver vazia (possui 0 itens). Se você usar FirstWITH uma expressão correspondente, como no seu exemplo, e essa condição não encontrar nenhum item, seu valor de retorno estará vazio.
OriolBG

5

Você também pode fazer isso

    Band[] objects = { new Band { Name = "Iron Maiden" } };
    first = objects.Where(o => o.Name == "Slayer")
        .DefaultIfEmpty(new Band { Name = "Black Sabbath" })
        .FirstOrDefault();   // returns "Black Sabbath" 

Isso usa apenas linq - yipee!


2
A única diferença entre esta resposta e a resposta da vitamina C é que esta utiliza em FirstOrDefaultvez de First. De acordo com msdn.microsoft.com/en-us/library/bb340482.aspx , o uso recomendado éFirst
Daniel

5

Na verdade, uso duas abordagens para evitar NullReferenceExceptionquando estou trabalhando com coleções:

public class Foo
{
    public string Bar{get; set;}
}
void Main()
{
    var list = new List<Foo>();
    //before C# 6.0
    string barCSharp5 = list.DefaultIfEmpty(new Foo()).FirstOrDefault().Bar;
    //C# 6.0 or later
    var barCSharp6 = list.FirstOrDefault()?.Bar;
}

Para C # 6.0 ou posterior:

Use ?.ou ?[para testar se é nulo antes de executar uma documentação de Operadores com Condição Nula de Acesso de Membro

Exemplo: var barCSharp6 = list.FirstOrDefault()?.Bar;

C # versão mais antiga:

Use DefaultIfEmpty()para recuperar um valor padrão se a sequência estiver vazia. Documentação MSDN

Exemplo: string barCSharp5 = list.DefaultIfEmpty(new Foo()).FirstOrDefault().Bar;


1
O operador de propagação nula não é permitido nos lamba da árvore de expressão.
Lars335

1

Em vez de YourCollection.FirstOrDefault(), você poderia usar YourCollection.DefaultIfEmpty(YourDefault).First()por exemplo.


Quando você acha que foi útil, pode votar. Esta não é uma resposta.
precisa saber é o seguinte

1

Eu apenas tive uma situação semelhante e estava procurando uma solução que me permitisse retornar um valor padrão alternativo sem cuidar dele no lado do chamador toda vez que eu precisar. O que geralmente fazemos no caso do Linq não suportar o que queremos é escrever uma nova extensão que cuide disso. Isso é o que eu fiz. Aqui está o que eu vim com (ainda não testado):

public static class EnumerableExtensions
{
    public static T FirstOrDefault<T>(this IEnumerable<T> items, T defaultValue)
    {
        foreach (var item in items)
        {
            return item;
        }
        return defaultValue;
    }

    public static T FirstOrDefault<T>(this IEnumerable<T> items, Func<T, bool> predicate, T defaultValue)
    {
        return items.Where(predicate).FirstOrDefault(defaultValue);
    }

    public static T LastOrDefault<T>(this IEnumerable<T> items, T defaultValue)
    {
        return items.Reverse().FirstOrDefault(defaultValue);
    }

    public static T LastOrDefault<T>(this IEnumerable<T> items, Func<T, bool> predicate, T defaultValue)
    {
        return items.Where(predicate).LastOrDefault(defaultValue);
    }
}

0

Eu sei que já faz um tempo, mas vou acrescentar a isso, com base na resposta mais popular, mas com uma pequena extensão, gostaria de compartilhar o seguinte:

static class ExtensionsThatWillAppearOnIEnumerables
{
    public static T FirstOr<T>(this IEnumerable<T> source, Func<T, bool> predicate, Func<T> alternate)
    {
        var thing = source.FirstOrDefault(predicate);
        if (thing != null)
            return thing;
        return alternate();
    }
}

Isso me permite chamá-lo em linha, como tal, com meu próprio exemplo, que estava tendo problemas com:

_controlDataResolvers.FirstOr(x => x.AppliesTo(item.Key), () => newDefaultResolver()).GetDataAsync(conn, item.ToList())

Então, para mim, eu só queria que um resolvedor padrão fosse usado em linha, posso fazer minha verificação usual e passar uma função para que uma classe não seja instanciada, mesmo que não seja usada, é uma função a ser executada quando necessário!


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.