FirstOrDefault: valor predeterminado que no sea nulo

142

Según tengo entendido, en Linq el método FirstOrDefault()puede devolver un Defaultvalor de algo distinto de nulo. Lo que no he resuelto es qué tipo de cosas que no sean nulas pueden ser devueltas por este método (y similar) cuando no hay elementos en el resultado de la consulta. ¿Hay alguna forma particular de que esto se pueda configurar para que, si no hay un valor para una consulta en particular, se devuelva algún valor predefinido como valor predeterminado?

Sachin Kainth
fuente
147
En lugar de YourCollection.FirstOrDefault(), podría usar, YourCollection.DefaultIfEmpty(YourDefault).First()por ejemplo.
pereza
66
He estado buscando algo como el comentario anterior durante bastante tiempo, me ayudó muchísimo. Esta debería ser la respuesta aceptada.
Brandon
El comentario anterior es la mejor respuesta.
Tom Padilla
En mi caso, la respuesta @sloth no funcionó cuando el valor devuelto es anulable y asignado a un no anulable. Solía MyCollection.Last().GetValueOrDefault(0)para eso. De lo contrario, la respuesta de @Jon Skeet a continuación es IMO correcta.
Jnr

Respuestas:

46

Caso general, no solo para los 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);

De nuevo, esto no se puede decir si hay era algo en su secuencia, o si el primer valor es el valor predeterminado.

Si te importa esto, podrías hacer 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;
    }
}

y usar como

var result = query.FirstOr(otherDefaultValue);

aunque, como señala el Sr. Steak, esto podría hacerse igual de bien .DefaultIfEmpty(...).First().

Rawling
fuente
Sus métodos genéricos necesitan <T>en sus nombres, pero lo más grave es que value == default(T)no funciona (porque quién sabe si Tpuede ser comparado por la igualdad?)
AakashM
Gracias por señalar eso, @AakashM; De hecho, he intentado esto ahora y creo que debería estar bien (aunque no me gusta el boxeo para los tipos de valor).
Rawling
3
@Rawling Use EqualityComparer<T>.Default.Equals(value, default(T))para evitar el boxeo y evitar una excepción si el valor esnull
Lukazoid
199

Según tengo entendido, en Linq el método FirstOrDefault () puede devolver un valor predeterminado de algo que no sea nulo.

No. O, más bien, siempre devuelve el valor predeterminado para el tipo de elemento ... que es una referencia nula, el valor nulo de un tipo de valor anulable o el valor natural "todos ceros" para un tipo de valor no anulable.

¿Hay alguna forma particular de que esto se pueda configurar para que, si no hay un valor para una consulta en particular, se devuelva algún valor predefinido como valor predeterminado?

Para los tipos de referencia, solo puede usar:

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

Por supuesto, esto también le dará el "otro valor predeterminado" si el primer valor está presente, pero es una referencia nula ...

Jon Skeet
fuente
Sé que la pregunta pide un tipo de referencia, pero su solución no funciona cuando los elementos son tipos de valores como int. Me gusta mucho más el uso de DefaultIfEmpty: src.Where(filter).DefaultIfEmpty(defaultValue).First(). Funciona tanto para el tipo de valor como para el tipo de referencia.
KFL
@KFL: Para los tipos de valores no anulables, probablemente también lo usaría, pero es más extenso para los casos anulables.
Jon Skeet
Impresionante control sobre los tipos de retorno cuando el valor predeterminado es nulo .. :)
Sundara Prabu
"No. O, más bien, siempre devuelve el valor predeterminado para el tipo de elemento ..." - Esto lo hizo por mí, en realidad ... ya que también entendí mal el significado del nombre de la función suponiendo que podría proporcionar cualquier valor predeterminado cuando sea necesario
Jesus Campon
Realmente me gustó este enfoque, pero cambié la "alternativa T" con "Func <T> alternate" y luego "return alternate ();" de esa manera no genero el objeto extra a menos que lo necesite. Especialmente útil si la función se llama muchas veces seguidas, el constructor es lento o la instancia alternativa del tipo ocupa mucha memoria.
Dan Violet Sagmiller
64

Puede usar DefaultIfEmpty seguido de Primero :

T customDefault = ...;
IEnumerable<T> mySequence = ...;
mySequence.DefaultIfEmpty(customDefault).First();
Vitamina C
fuente
Me encanta la idea de DefaultIfEmpty- que funciona con las API de todo lo que necesitan un defecto se debe especificar: First(), Last(), etc. Como usuario, usted no necesita recordar qué API permite especificar por defecto que no lo hacen. ¡Muy elegante!
KFL
Esta es en gran medida la respuesta del nido.
Jesse Williams
19

De la documentación para FirstOrDefault

[Devuelve] predeterminado (TSource) si la fuente está vacía;

De la documentación por defecto (T) :

la palabra clave predeterminada, que devolverá nulo para los tipos de referencia y cero para los tipos de valor numérico. Para las estructuras, devolverá cada miembro de la estructura inicializado a cero o nulo, dependiendo de si son tipos de valor o de referencia. Para los tipos de valores anulables, default devuelve un System.Nullable, que se inicializa como cualquier estructura.

Por lo tanto, el valor predeterminado puede ser nulo o 0 dependiendo de si el tipo es una referencia o un tipo de valor, pero no puede controlar el comportamiento predeterminado.

RB.
fuente
6

Copiado del comentario de @sloth

En lugar de YourCollection.FirstOrDefault(), podría usar, YourCollection.DefaultIfEmpty(YourDefault).First()por ejemplo.

Ejemplo:

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)),
    };
Matas Vaitkevicius
fuente
2
Tenga en cuenta que DefaultIfEmptydevuelve el valor predeterminado SI la colección está vacía (tiene 0 elementos). Si usa FirstWITH con una expresión coincidente como en su ejemplo y esa condición no encuentra ningún artículo, su valor de retorno estará vacío.
OriolBG
5

También puedes hacer esto

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

Esto usa solo linq - yipee!

BurnWithLife
fuente
2
La única diferencia entre esta respuesta y la respuesta de la vitamina C es que esta usa en FirstOrDefaultlugar de First. De acuerdo con msdn.microsoft.com/en-us/library/bb340482.aspx , el uso recomendado esFirst
Daniel
5

En realidad, uso dos enfoques para evitar NullReferenceExceptioncuando estoy trabajando con colecciones:

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 o posterior:

Use ?.o ?[para probar si es nulo antes de realizar un acceso de miembro Documentación de operadores condicional nulo

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

C # versión anterior:

Úselo DefaultIfEmpty()para recuperar un valor predeterminado si la secuencia está vacía. Documentación de MSDN

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

Samuel Diogo
fuente
1
El operador de propagación nulo no está permitido en el árbol de expresión lamba.
Lars335
1

En lugar de YourCollection.FirstOrDefault(), podría usar, YourCollection.DefaultIfEmpty(YourDefault).First()por ejemplo.

Raja
fuente
Cuando creas que fue útil, puedes votar. Esta no es una respuesta.
jannagy02
1

Acabo de tener una situación similar y estaba buscando una solución que me permitiera devolver un valor predeterminado alternativo sin ocuparme de él cada vez que lo necesito. Lo que solemos hacer en caso de que Linq no admita lo que queremos es escribir una nueva extensión que se encargue de ello. Eso fue lo que hice. Esto es lo que se me ocurrió (aunque no probado):

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);
    }
}
harri
fuente
0

Sé que ha pasado un tiempo, pero agregaré a esto, basado en la respuesta más popular, pero con una pequeña extensión, me gustaría compartir lo siguiente:

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();
    }
}

Esto me permite llamarlo en línea como tal con mi propio ejemplo con el que estaba teniendo problemas:

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

Entonces, para mí, solo quería que un resolutor predeterminado se usara en línea, puedo hacer mi verificación habitual y luego pasar una función para que no se instancia una clase, incluso si no se usa, ¡es una función para ejecutar cuando sea necesario!

Aaron Gibson
fuente
-2

Usar en DefaultIfEmpty()lugar de FirstOrDefault().

Abhishek Singh
fuente