FirstOrDefault: Giá trị mặc định khác null


142

Theo tôi hiểu, trong Linq phương thức FirstOrDefault()có thể trả về Defaultgiá trị của một thứ khác không phải là null. Điều tôi chưa giải quyết được là những thứ khác ngoài null có thể được trả về bằng phương thức (và tương tự) này khi không có mục nào trong kết quả truy vấn. Có cách nào cụ thể mà điều này có thể được thiết lập để nếu không có giá trị cho một truy vấn cụ thể, một số giá trị được xác định trước được trả về làm giá trị mặc định không?


147
Thay vì YourCollection.FirstOrDefault(), bạn có thể sử dụng YourCollection.DefaultIfEmpty(YourDefault).First()ví dụ.
lười

6
Tôi đã tìm kiếm một cái gì đó giống như nhận xét ở trên trong một thời gian dài, nó đã giúp rất nhiều. Đây phải là câu trả lời được chấp nhận.
Brandon

Nhận xét trên là câu trả lời tốt nhất.
Tom Padilla

Trong trường hợp của tôi, câu trả lời @sloth không hoạt động khi giá trị được trả về là nullable và được gán cho một nullable. Tôi đã sử dụng MyCollection.Last().GetValueOrDefault(0)cho điều đó. Nếu không, câu trả lời @Jon Skeet dưới đây là IMO đúng.
Jnr

Câu trả lời:


46

Trường hợp chung, không chỉ cho các loại giá trị:

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

Một lần nữa, điều này không thể thực sự nói nếu có bất cứ điều gì theo thứ tự của bạn, hoặc nếu giá trị đầu tiên là mặc định.

Nếu bạn quan tâm về điều này, bạn có thể làm một cái gì đó như

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

và sử dụng như

var result = query.FirstOr(otherDefaultValue);

mặc dù như ông Steak chỉ ra điều này cũng có thể được thực hiện bằng cách .DefaultIfEmpty(...).First().


Các phương thức chung của bạn cần có <T>tên của chúng, nhưng nghiêm trọng hơn là value == default(T)nó không hoạt động (vì ai biết liệu Tcó thể so sánh được sự bình đẳng không?)
AakashM

Cảm ơn bạn đã chỉ ra rằng, @AakashM; Tôi thực sự đã thử điều này ngay bây giờ và tôi nghĩ nó sẽ ổn (mặc dù tôi không thích quyền anh cho các loại giá trị).
Rawling

3
@Rawling Sử dụng EqualityComparer<T>.Default.Equals(value, default(T))để tránh quyền anh và tránh ngoại lệ nếu giá trị lànull
Lukazoid

199

Theo tôi hiểu, trong Linq, phương thức FirstOrDefault () có thể trả về giá trị Mặc định của một thứ khác không phải là null.

Không. Hay nói đúng hơn, nó luôn trả về giá trị mặc định cho loại phần tử ... là tham chiếu null, giá trị null của loại giá trị null hoặc giá trị "tất cả số không" tự nhiên cho loại giá trị không null.

Có cách nào cụ thể mà điều này có thể được thiết lập để nếu không có giá trị cho một truy vấn cụ thể, một số giá trị được xác định trước được trả về làm giá trị mặc định không?

Đối với các loại tham chiếu, bạn chỉ có thể sử dụng:

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

Tất nhiên điều này cũng sẽ cung cấp cho bạn "giá trị mặc định khác" nếu có giá trị đầu tiên, nhưng là một tham chiếu null ...


Tôi biết câu hỏi yêu cầu loại tham chiếu nhưng giải pháp của bạn không hoạt động khi các phần tử là loại giá trị như thế nào int. Tôi rất thích việc sử dụng DefaultIfEmpty: src.Where(filter).DefaultIfEmpty(defaultValue).First(). Hoạt động cho cả loại giá trị và loại tham chiếu.
KFL

@KFL: Đối với các loại giá trị không thể rỗng, tôi cũng có thể sử dụng loại đó - nhưng nó dài hơn đối với các trường hợp không có giá trị.
Jon Skeet

Kiểm soát tuyệt vời đối với các loại trả về khi mặc định là null .. :)
Sundara Mitchu 17/2/18

"Không. Hay đúng hơn, nó luôn trả về giá trị mặc định cho loại phần tử ..." - Điều này thực sự đã làm cho tôi, vì tôi cũng hiểu sai ý nghĩa của tên hàm bằng cách giả sử bạn có thể cung cấp bất kỳ giá trị mặc định nào khi được yêu cầu
Jesus Campon

Tôi thực sự thích cách tiếp cận này, nhưng tôi đã thay đổi "T thay thế" bằng "Func <T> thay thế", và sau đó "return Alternate ();" bằng cách đó tôi không tạo thêm đối tượng trừ khi tôi cần. Đặc biệt hữu ích nếu hàm được gọi nhiều lần liên tiếp, hàm tạo chậm hoặc thể hiện thay thế của loại chiếm nhiều bộ nhớ.
Dan Violet Sagmiller

64

Bạn có thể sử dụng Default IfEmpty theo sau bởi Đầu tiên :

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

Tôi thích ý tưởng về DefaultIfEmpty- nó hoạt động với tất cả các API cần một mặc định được xác định: First(), Last(), vv Là một người sử dụng, bạn không cần phải nhớ mà API cho phép để xác định mặc định nào không. Rất thanh lịch!
KFL

Đây là rất nhiều câu trả lời tổ.
Jesse Williams

19

Từ tài liệu cho FirstOrDefault

[Trả về] mặc định (TSource) nếu nguồn trống;

Từ tài liệu mặc định (T) :

từ khóa mặc định, sẽ trả về null cho các loại tham chiếu và 0 cho các loại giá trị số. Đối với các cấu trúc, nó sẽ trả về mỗi thành viên của cấu trúc được khởi tạo về 0 hoặc null tùy thuộc vào việc chúng là các loại giá trị hoặc tham chiếu. Đối với các loại giá trị nullable, mặc định trả về System.Nullable, được khởi tạo giống như bất kỳ cấu trúc nào.

Do đó, giá trị mặc định có thể là null hoặc 0 tùy thuộc vào loại đó là loại tham chiếu hay loại giá trị, nhưng bạn không thể kiểm soát hành vi mặc định.


6

Sao chép từ nhận xét của @sloth

Thay vì YourCollection.FirstOrDefault(), bạn có thể sử dụng YourCollection.DefaultIfEmpty(YourDefault).First()ví dụ.

Thí dụ:

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
Lưu ý rằng DefaultIfEmptytrả về mặc định NẾU bộ sưu tập trống (có 0 mục). Nếu bạn sử dụng FirstVỚI biểu thức khớp như trong ví dụ của bạn và điều kiện đó không tìm thấy bất kỳ mục nào, giá trị trả về của bạn sẽ trống.
OriolBG

5

Bạn cũng có thể làm điều này

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

Điều này chỉ sử dụng linq - yipee!


2
Sự khác biệt duy nhất giữa câu trả lời này và câu trả lời của Vitamin C là cái này sử dụng FirstOrDefaultthay vì First. Theo msdn.microsoft.com/en-us/l Library / bb340482.aspx , cách sử dụng được đề xuất làFirst
Daniel

5

Trên thực tế, tôi sử dụng hai cách tiếp cận để tránh NullReferenceExceptionkhi tôi làm việc với các bộ sưu tập:

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

Đối với C # 6.0 trở lên:

Sử dụng ?.hoặc ?[để kiểm tra nếu là null trước khi thực hiện truy cập thành viên Tài liệu khai thác có điều kiện Null

Thí dụ: var barCSharp6 = list.FirstOrDefault()?.Bar;

Phiên bản cũ hơn của C #:

Sử dụng DefaultIfEmpty()để lấy giá trị mặc định nếu chuỗi trống. Tài liệu MSDN

Thí dụ: string barCSharp5 = list.DefaultIfEmpty(new Foo()).FirstOrDefault().Bar;


1
Toán tử lan truyền null không được phép trong biểu thức lamba của cây.
Lars335

1

Thay vì YourCollection.FirstOrDefault(), bạn có thể sử dụng YourCollection.DefaultIfEmpty(YourDefault).First()ví dụ.


Khi bạn nghĩ rằng nó hữu ích, bạn có thể nâng cấp. Đây không phải là một câu trả lời.
jannagy02

1

Tôi chỉ gặp một tình huống tương tự và đang tìm kiếm một giải pháp cho phép tôi trả về một giá trị mặc định thay thế mà không cần quan tâm đến nó ở phía người gọi mỗi khi tôi cần. Những gì chúng ta thường làm trong trường hợp Linq không hỗ trợ những gì chúng ta muốn, là viết một phần mở rộng mới chăm sóc nó. Đó là những gì tôi đã làm. Đây là những gì tôi đã đưa ra (mặc dù không được thử nghiệm):

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

Tôi biết đã được một lúc nhưng Ill thêm vào điều này, dựa trên câu trả lời phổ biến nhất nhưng với một chút mở rộng Id muốn chia sẻ bên dưới:

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

Điều này cho phép tôi gọi nó là nội tuyến như vậy với ví dụ của riêng tôi, tôi đã gặp vấn đề với:

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

Vì vậy, đối với tôi, tôi chỉ muốn một trình phân giải mặc định được sử dụng nội tuyến, tôi có thể thực hiện kiểm tra thông thường của mình và sau đó chuyển vào một hàm để một lớp không được khởi tạo ngay cả khi không được sử dụng, thay vào đó là một hàm để thực thi khi được yêu cầu!


Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.