FirstOrDefault: ค่าเริ่มต้นอื่นที่ไม่ใช่ null


142

ดังที่ฉันเข้าใจแล้วใน Linq วิธีFirstOrDefault()สามารถคืนDefaultค่าของสิ่งอื่นที่ไม่ใช่ null สิ่งที่ฉันไม่ได้ทำคือสิ่งอื่นที่ไม่ใช่ null สามารถส่งคืนโดยวิธีนี้ (และคล้ายกัน) เมื่อไม่มีรายการในผลลัพธ์แบบสอบถาม มีวิธีใดที่สามารถตั้งค่านี้ได้ดังนั้นหากไม่มีค่าสำหรับการสืบค้นเฉพาะค่าที่กำหนดไว้ล่วงหน้าจะถูกส่งกลับเป็นค่าเริ่มต้นหรือไม่


147
แทนที่จะเป็นเช่นYourCollection.FirstOrDefault()นั้นคุณสามารถใช้YourCollection.DefaultIfEmpty(YourDefault).First()เป็นตัวอย่างได้
เฉื่อยชา

6
ฉันได้รับการมองหาบางอย่างเช่นความคิดเห็นข้างต้นเป็นเวลานานมันช่วยอย่างมาก นี่ควรเป็นคำตอบที่ยอมรับได้
Brandon

ความคิดเห็นข้างต้นเป็นคำตอบที่ดีที่สุด
Tom Padilla

ในกรณีที่คำตอบ @sloth ของฉันไม่ทำงานเมื่อค่าที่ส่งคืนเป็นโมฆะและกำหนดให้เป็นค่าที่ไม่เป็นโมฆะ ฉันใช้MyCollection.Last().GetValueOrDefault(0)สิ่งนั้น มิฉะนั้นคำตอบ @Jon Skeet ด้านล่างนี้จะถูกต้อง IMO
Jnr

คำตอบ:


46

กรณีทั่วไปไม่เพียง แต่สำหรับประเภทค่า:

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

อีกครั้งนี้ไม่สามารถจริงๆบอกได้ว่ามีเป็นสิ่งที่อยู่ในลำดับของคุณหรือถ้าค่าแรกเป็นค่าเริ่มต้น

หากคุณสนใจสิ่งนี้คุณสามารถทำอะไรบางอย่างเช่น

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

และใช้เป็น

var result = query.FirstOr(otherDefaultValue);

.DefaultIfEmpty(...).First()แม้ว่าขณะที่นายสเต็กชี้ให้เห็นนี้สามารถทำได้เพียงเช่นกันโดย


วิธีการทั่วไปของคุณต้องการ<T>ในชื่อของพวกเขา แต่ที่รุนแรงกว่านั้นvalue == default(T)ไม่ได้ผล (เพราะใครจะรู้ว่าTสามารถนำมาเปรียบเทียบเพื่อความเท่าเทียมกันได้หรือไม่)
AakashM

ขอบคุณสำหรับการชี้ให้เห็นว่า @AakashM; ตอนนี้ฉันได้ลองแล้วและฉันคิดว่ามันควรจะโอเค (แม้ว่าฉันจะไม่ชอบมวยสำหรับประเภทค่า)
Rawling

3
@Rawling ใช้EqualityComparer<T>.Default.Equals(value, default(T))เพื่อหลีกเลี่ยงการชกมวยและหลีกเลี่ยงข้อยกเว้นหากค่าคือnull
Lukazoid

199

ตามที่ฉันเข้าใจใน Linq วิธี FirstOrDefault () สามารถคืนค่าเริ่มต้นของสิ่งอื่นที่ไม่ใช่ null

ไม่หรือมันจะส่งกลับค่าเริ่มต้นสำหรับประเภทองค์ประกอบ ... ซึ่งเป็นการอ้างอิงที่เป็นโมฆะหรือค่าที่เป็นโมฆะของประเภทค่าที่เป็นโมฆะหรือค่า "ทั้งหมดเป็นศูนย์" สำหรับประเภทของค่าที่ไม่เป็นโมฆะ

มีวิธีใดที่สามารถตั้งค่านี้ได้ดังนั้นหากไม่มีค่าสำหรับการสืบค้นเฉพาะค่าที่กำหนดไว้ล่วงหน้าจะถูกส่งกลับเป็นค่าเริ่มต้นหรือไม่

สำหรับประเภทการอ้างอิงคุณสามารถใช้:

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

หลักสูตรนี้จะยังให้คุณได้ "ค่าเริ่มต้นอื่น ๆ" ถ้าค่าแรกเป็นปัจจุบัน แต่อ้างอิงโมฆะ ...


ฉันรู้ว่าคำถามจะถามสำหรับประเภทการอ้างอิง intแต่วิธีการแก้ปัญหาของคุณไม่ทำงานเมื่อองค์ประกอบประเภทค่าเช่น ผมชอบมากใช้:DefaultIfEmpty src.Where(filter).DefaultIfEmpty(defaultValue).First()ใช้ได้ทั้งประเภทค่าและประเภทอ้างอิง
KFL

@KFL: สำหรับประเภทค่าที่ไม่เป็นโมฆะฉันก็อาจใช้มันเช่นกัน - แต่มันยืดยาวมากขึ้นสำหรับกรณีที่ไม่มีค่า
Jon Skeet

การควบคุมประเภทผลตอบแทนที่ยอดเยี่ยมเมื่อค่าเริ่มต้นเป็นโมฆะ .. :)
Sundara Prabu

"ไม่หรือค่อนข้างจะส่งคืนค่าเริ่มต้นสำหรับประเภทองค์ประกอบ ... " - สิ่งนี้ทำเพื่อฉันจริง ๆ แล้ว .. เนื่องจากฉันยังเข้าใจผิดความหมายของชื่อฟังก์ชันโดยสมมติว่าคุณสามารถให้ค่าเริ่มต้นเมื่อจำเป็น
Jesus Campon

ฉันชอบวิธีนี้มาก แต่ฉันเปลี่ยน "T Alternate" เป็น "Func <T> Alternate" แล้วเปลี่ยน "return Alternate ();" ด้วยวิธีนี้ฉันไม่ได้สร้างวัตถุพิเศษเว้นแต่ฉันต้องการ มีประโยชน์อย่างยิ่งหากฟังก์ชั่นถูกเรียกหลายครั้งในแถวคอนสตรัคเตอร์ช้าหรืออินสแตนซ์อื่นของประเภทนั้นใช้หน่วยความจำมาก
Dan Violet Sagmiller

64

คุณสามารถใช้DefaultIfEmptyตามด้วยFirst :

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

ฉันรักความคิดของDefaultIfEmpty- มันทำงานร่วมกับทุก APIs ที่ต้องเริ่มต้นที่จะระบุFirst(), Last()ฯลฯ ในฐานะที่เป็นผู้ใช้คุณไม่จำเป็นต้องจำ API ที่อนุญาตให้มีการระบุค่าเริ่มต้นที่ทำไม่ได้ สง่างามมาก!
KFL

นี่คือคำตอบที่รังมาก
Jesse Williams

19

จากเอกสารสำหรับFirstOrDefault

[ส่งคืน] ค่าเริ่มต้น (แหล่งที่มา) หากแหล่งข้อมูลว่างเปล่า

จากเอกสารสำหรับค่าเริ่มต้น (T) :

คำหลักเริ่มต้นซึ่งจะส่งกลับ null สำหรับประเภทอ้างอิงและศูนย์สำหรับประเภทค่าตัวเลข สำหรับ structs มันจะคืนค่าสมาชิกของโครงสร้างเริ่มต้นเป็นศูนย์หรือเป็นโมฆะขึ้นอยู่กับว่าเป็นประเภทค่าหรือการอ้างอิง สำหรับประเภทค่า nullable เริ่มต้นส่งกลับ System.Nullable ซึ่งเริ่มต้นได้เหมือน struct ใด ๆ

ดังนั้นค่าเริ่มต้นอาจเป็นโมฆะหรือ 0 ขึ้นอยู่กับว่าประเภทนั้นเป็นข้อมูลอ้างอิงหรือประเภทค่า แต่คุณไม่สามารถควบคุมพฤติกรรมเริ่มต้นได้


6

คัดลอกมาจากความคิดเห็นโดย @sloth

แทนที่จะเป็นเช่นYourCollection.FirstOrDefault()นั้นคุณสามารถใช้YourCollection.DefaultIfEmpty(YourDefault).First()เป็นตัวอย่างได้

ตัวอย่าง:

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
โปรดทราบว่าDefaultIfEmptyส่งกลับค่าเริ่มต้นถ้าคอลเลกชันว่างเปล่า (มี 0 รายการ) หากคุณใช้Firstกับนิพจน์ที่ตรงกันเช่นในตัวอย่างของคุณและเงื่อนไขนั้นไม่พบรายการใด ๆ ค่าส่งคืนของคุณจะว่างเปล่า
OriolBG

5

คุณสามารถทำได้

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

สิ่งนี้ใช้เพียง linq - yipee!


2
ข้อแตกต่างระหว่างคำตอบนี้และคำตอบของวิตามินซีเป็นที่หนึ่งนี้ใช้แทนFirstOrDefault Firstตามmsdn.microsoft.com/en-us/library/bb340482.aspxการใช้งานที่แนะนำคือFirst
Daniel

5

จริงๆแล้วฉันใช้สองวิธีในการหลีกเลี่ยงNullReferenceExceptionเมื่อฉันทำงานกับคอลเลกชัน:

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

สำหรับ C # 6.0 หรือใหม่กว่า:

ใช้?.หรือ?[เพื่อทดสอบว่าเป็นโมฆะก่อนดำเนินการเข้าถึงสมาชิกเอกสารประกอบการ Null-conditional

ตัวอย่าง: var barCSharp6 = list.FirstOrDefault()?.Bar;

รุ่น C # เก่ากว่า:

ใช้DefaultIfEmpty()เพื่อดึงค่าเริ่มต้นหากลำดับว่างเปล่า เอกสาร MSDN

ตัวอย่าง: string barCSharp5 = list.DefaultIfEmpty(new Foo()).FirstOrDefault().Bar;


1
ไม่อนุญาตให้ใช้โอเปอเรเตอร์การเผยแพร่ null ในนิพจน์ต้นไม้ lamba
Lars335

1

แทนที่จะเป็นเช่นYourCollection.FirstOrDefault()นั้นคุณสามารถใช้YourCollection.DefaultIfEmpty(YourDefault).First()เป็นตัวอย่างได้


เมื่อคุณคิดว่ามันมีประโยชน์คุณสามารถโหวตขึ้นได้ นี่ไม่ใช่คำตอบ
jannagy02

1

ฉันเพิ่งมีสถานการณ์ที่คล้ายกันและกำลังมองหาวิธีการแก้ปัญหาที่ช่วยให้ฉันกลับค่าเริ่มต้นทางเลือกโดยไม่ต้องดูแลที่ฝ่ายโทรทุกครั้งที่ฉันต้องการมัน สิ่งที่เรามักจะทำในกรณีที่ Linq ไม่สนับสนุนสิ่งที่เราต้องการคือการเขียนนามสกุลใหม่ที่ดูแลมัน นั่นคือสิ่งที่ฉันทำ นี่คือสิ่งที่ฉันมาด้วย (ไม่ได้ทดสอบว่า):

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

ฉันรู้ว่ามันนานแล้ว แต่ป่วยเพิ่มสิ่งนี้ขึ้นอยู่กับคำตอบที่ได้รับความนิยมมากที่สุด แต่มี ID ส่วนขยายเล็กน้อยชอบแชร์ด้านล่าง:

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

สิ่งนี้ทำให้ฉันสามารถเรียกมันได้แบบอินไลน์เช่นนี้กับตัวอย่างของฉันเองฉันมีปัญหากับ:

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

ดังนั้นสำหรับฉันฉันแค่ต้องการให้ตัวแก้ไขเริ่มต้นใช้อินไลน์ฉันสามารถตรวจสอบตามปกติของฉันแล้วส่งผ่านฟังก์ชั่นเพื่อให้ชั้นเรียนไม่ได้ถูกสร้างอินสแตนซ์แม้ว่าจะไม่ได้ใช้งาน


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.