วิธีที่เหมาะสมในการตรวจสอบค่าว่างคืออะไร?


122

ฉันชอบตัวดำเนินการการรวมกันเป็นโมฆะเพราะทำให้ง่ายต่อการกำหนดค่าเริ่มต้นสำหรับประเภทที่ว่าง

 int y = x ?? -1;

ที่ดี xแต่ถ้าฉันต้องทำบางสิ่งบางอย่างที่เรียบง่ายด้วย ตัวอย่างเช่นถ้าฉันต้องการตรวจสอบSessionฉันมักจะต้องเขียนอะไรที่ละเอียดกว่านี้

ฉันหวังว่าฉันจะทำได้:

string y = Session["key"].ToString() ?? "none";

แต่คุณทำไม่ได้เนื่องจาก.ToString()ถูกเรียกก่อนการตรวจสอบค่าว่างจึงล้มเหลวหากSession["key"]เป็นโมฆะ ฉันจะทำสิ่งนี้:

string y = Session["key"] == null ? "none" : Session["key"].ToString();

มันใช้งานได้และดีกว่าในความคิดของฉันมากกว่าทางเลือกสามบรรทัด:

string y = "none";
if (Session["key"] != null)
    y = Session["key"].ToString();

แม้ว่าจะได้ผล แต่ฉันก็ยังอยากรู้ว่ามีวิธีที่ดีกว่านี้หรือไม่ ดูเหมือนว่าไม่ว่าอะไรก็ตามที่ฉันต้องอ้างอิงSession["key"]สองครั้งเสมอ หนึ่งครั้งสำหรับการตรวจสอบและอีกครั้งสำหรับงานที่มอบหมาย ความคิดใด ๆ ?


20
นี่คือเมื่อฉันต้องการ C # มี "ผู้ประกอบการนำทางที่ปลอดภัย" ( .?) เช่นGroovy มี
Cameron

2
@ คาเมรอน: นี่คือตอนที่ฉันต้องการให้ C # ถือว่าประเภทที่เป็นโมฆะ (รวมถึงประเภทอ้างอิง) เป็น monad ดังนั้นคุณไม่จำเป็นต้องมี "ตัวดำเนินการนำทางที่ปลอดภัย"
Jon Purdy

3
ผู้คิดค้นการอ้างอิงที่เป็นโมฆะเรียกมันว่า "ความผิดพลาดพันล้านดอลลาร์" ของเขาและฉันมักจะเห็นด้วย ดูinfoq.com/presentations/…
Jamie Ide

ข้อผิดพลาดที่แท้จริงของเขาคือการผสมประเภทที่เป็นโมฆะและไม่ใช้ภาษาที่ไม่ปลอดภัย (ไม่บังคับใช้ภาษา)
MSalters

@JamieIde ขอบคุณสำหรับลิงค์ที่น่าสนใจมาก :)
BobRodes

คำตอบ:


182

แล้ว

string y = (Session["key"] ?? "none").ToString();

79
แรงมาแรงกับเจ้านี้
Chev

2
@ แมทธิว: ไม่เพราะค่าเซสชันเป็นประเภท Object
BlackBear

1
@BlackBear แต่ค่าที่ส่งกลับส่วนใหญ่น่าจะเป็นสตริงดังนั้นการแสดงจึงถูกต้อง
Firo

นี่เป็นคำตอบที่ตรงที่สุดสำหรับคำถามของฉันดังนั้นฉันจึงทำเครื่องหมายคำตอบ แต่วิธีการขยายของ Jon Skeet เป็นวิธีที่.ToStringOrDefault()ฉันชอบในการทำ อย่างไรก็ตามฉันใช้คำตอบนี้ในวิธีการขยายของจอน;)
Chev

10
ฉันไม่ชอบสิ่งนี้เพราะหากคุณมีวัตถุประเภทอื่นยัดไส้ในเซสชันนอกเหนือจากที่คุณคาดไว้คุณอาจซ่อนข้อบกพร่องบางอย่างในโปรแกรมของคุณ ฉันควรใช้เซฟนักแสดงเพราะคิดว่าน่าจะพบข้อผิดพลาดเร็วขึ้น นอกจากนี้ยังหลีกเลี่ยงการเรียก ToString () บนวัตถุสตริง
tvanfosson

130

หากคุณทำสิ่งนี้บ่อยโดยเฉพาะToString()คุณสามารถเขียนวิธีการขยาย:

public static string NullPreservingToString(this object input)
{
    return input == null ? null : input.ToString();
}

...

string y = Session["key"].NullPreservingToString() ?? "none";

หรือวิธีการที่เป็นค่าเริ่มต้นแน่นอน:

public static string ToStringOrDefault(this object input, string defaultValue)
{
    return input == null ? defaultValue : input.ToString();
}

...

string y = Session["key"].ToStringOrDefault("none");

16
.ToStringOrDefault()เรียบง่ายและสง่างาม ทางออกที่ดี
Chev

7
ฉันไม่เห็นด้วยกับเรื่องนี้เลย วิธีการขยายบนobjectเป็นการสาปแช่งและทำให้ฐานของโค้ดเป็นขยะและวิธีการขยายที่ทำงานโดยไม่มีข้อผิดพลาดกับค่าว่างthisนั้นเป็นความชั่วร้ายที่บริสุทธิ์
Nick Larsen

10
@NickLarsen: ทุกอย่างอยู่ในความดูแลฉันพูด วิธีการขยายที่ทำงานกับ null จะมีประโยชน์มาก IMO - ตราบใดที่มีความชัดเจนเกี่ยวกับสิ่งที่ทำ
Jon Skeet

3
@ one.beat.consumer: ใช่ หากเป็นเพียงการจัดรูปแบบ (หรือการพิมพ์ผิด) นั่นอาจเป็นเรื่องหนึ่ง แต่การเปลี่ยนชื่อวิธีการที่ผู้เขียนเลือกนั้นเกินกว่าการแก้ไขที่เหมาะสมตามปกติ IMO
Jon Skeet

6
@ one.beat.consumer: เมื่อแก้ไขไวยากรณ์และพิมพ์ผิดก็ไม่เป็นไร แต่การเปลี่ยนชื่อที่ใครบางคน (ใครก็ได้ไม่ใช่แค่ฉัน) เลือกโดยเจตนาอย่างชัดเจนให้ความรู้สึกแตกต่างกับฉัน ณ จุดนั้นฉันขอแนะนำเป็นความคิดเห็นแทน
Jon Skeet

21

คุณยังสามารถใช้asซึ่งให้ผลnullหากการแปลงล้มเหลว:

Session["key"] as string ?? "none"

นี้จะกลับมา"none"แม้ว่าบางคนยัดในintSession["key"]


1
วิธีนี้ใช้ได้เฉพาะเมื่อคุณไม่ต้องการToString()ในตอนแรก
Abel

1
ฉันแปลกใจที่ยังไม่มีใครโหวตคำตอบนี้ นี่เป็นความหมายที่แตกต่างอย่างสิ้นเชิงกับสิ่งที่ OP ต้องการทำ
Timwi

@Timwi: OP ใช้ToString()เพื่อแปลงวัตถุที่มีสตริงเป็นสตริง คุณสามารถทำเช่นเดียวกันกับหรือobj as string (string)objเป็นสถานการณ์ที่พบได้บ่อยใน ASP.NET
Andomar

5
@Andomar: ไม่ OP กำลังเรียกToString()ใช้วัตถุ (กล่าวคือSession["key"]) ซึ่งเขาไม่ได้กล่าวถึงประเภทใด อาจเป็นวัตถุชนิดใดก็ได้ไม่จำเป็นต้องเป็นสตริง
Timwi

13

หากเป็นเสมอstringคุณสามารถส่ง:

string y = (string)Session["key"] ?? "none";

นี้มีประโยชน์บ่นแทนจากที่ซ่อนความผิดพลาดถ้ามีคนยัดหรือสิ่งที่อยู่ในint Session["key"];)


10

วิธีแก้ปัญหาที่แนะนำทั้งหมดนั้นดีและตอบคำถามได้ ดังนั้นนี่เป็นเพียงการขยายออกไปเล็กน้อย ปัจจุบันคำตอบส่วนใหญ่จัดการเฉพาะการตรวจสอบความถูกต้องและประเภทสตริงเท่านั้น คุณสามารถขยายStateBagวัตถุเพื่อรวมGetValueOrDefaultวิธีการทั่วไปคล้ายกับคำตอบที่โพสต์โดย Jon Skeet

วิธีการขยายทั่วไปอย่างง่ายที่ยอมรับสตริงเป็นคีย์จากนั้นพิมพ์ตรวจสอบวัตถุเซสชัน ถ้าออบเจ็กต์เป็นโมฆะหรือไม่ใช่ประเภทเดียวกันค่าดีฟอลต์จะถูกส่งกลับไม่เช่นนั้นค่าเซสชันจะถูกพิมพ์อย่างรุนแรง

อะไรทำนองนี้

/// <summary>
/// Gets a value from the current session, if the type is correct and present
/// </summary>
/// <param name="key">The session key</param>
/// <param name="defaultValue">The default value</param>
/// <returns>Returns a strongly typed session object, or default value</returns>
public static T GetValueOrDefault<T>(this HttpSessionState source, string key, T defaultValue)
{
    // check if the session object exists, and is of the correct type
    object value = source[key]
    if (value == null || !(value is T))
    {
        return defaultValue;
    }

    // return the session object
    return (T)value;
}

1
คุณสามารถรวมตัวอย่างการใช้งานสำหรับวิธีการขยายนี้ได้หรือไม่? StateBag ไม่จัดการกับสถานะการดูและไม่ใช่เซสชัน? ฉันใช้ ASP.NET MVC 3 ดังนั้นฉันจึงไม่สามารถเข้าถึงสถานะการดูได้ง่ายๆ HttpSessionStateฉันคิดว่าคุณต้องการที่จะขยาย
Chev

คำตอบนี้ต้องการการดึงค่า 3x และ 2 casts หากทำได้สำเร็จ (ฉันรู้ว่ามันเป็นพจนานุกรม แต่ผู้เริ่มต้นอาจใช้วิธีปฏิบัติที่คล้ายกันกับวิธีการที่มีราคาแพง)
Jake Berger

3
T value = source[key] as T; return value ?? defaultValue;
เจคเบอร์เกอร์

1
@jberger การแคสต์เป็นค่าโดยใช้ "เป็น" ไม่สามารถเข้าถึงได้เนื่องจากไม่มีข้อ จำกัด ของคลาสสำหรับประเภททั่วไปเนื่องจากคุณอาจต้องการส่งคืนค่าเช่นบูล @AlexFord ขออภัยคุณต้องการขยายเวลาHttpSessionStateสำหรับเซสชั่น :)
Richard

จริง ตามที่ริชาร์ดตั้งข้อสังเกตต้องใช้ข้อ จำกัด (... และอีกวิธีหากคุณต้องการใช้ประเภทค่า)
Jake Berger

7

เราใช้วิธีที่เรียกว่าNullOr.

การใช้

// Call ToString() if it’s not null, otherwise return null
var str = myObj.NullOr(obj => obj.ToString());

// Supply default value for when it’s null
var str = myObj.NullOr(obj => obj.ToString()) ?? "none";

// Works with nullable return values, too —
// this is properly typed as “int?” (nullable int)
// even if “Count” is just int
var count = myCollection.NullOr(coll => coll.Count);

// Works with nullable input types, too
int? unsure = 47;
var sure = unsure.NullOr(i => i.ToString());

แหล่ง

/// <summary>Provides a function delegate that accepts only value types as return types.</summary>
/// <remarks>This type was introduced to make <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncStruct{TInput,TResult})"/>
/// work without clashing with <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncClass{TInput,TResult})"/>.</remarks>
public delegate TResult FuncStruct<in TInput, TResult>(TInput input) where TResult : struct;

/// <summary>Provides a function delegate that accepts only reference types as return types.</summary>
/// <remarks>This type was introduced to make <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncClass{TInput,TResult})"/>
/// work without clashing with <see cref="ObjectExtensions.NullOr{TInput,TResult}(TInput,FuncStruct{TInput,TResult})"/>.</remarks>
public delegate TResult FuncClass<in TInput, TResult>(TInput input) where TResult : class;

/// <summary>Provides extension methods that apply to all types.</summary>
public static class ObjectExtensions
{
    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult NullOr<TInput, TResult>(this TInput input, FuncClass<TInput, TResult> lambda) where TResult : class
    {
        return input == null ? null : lambda(input);
    }

    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult? NullOr<TInput, TResult>(this TInput input, Func<TInput, TResult?> lambda) where TResult : struct
    {
        return input == null ? null : lambda(input);
    }

    /// <summary>Returns null if the input is null, otherwise the result of the specified lambda when applied to the input.</summary>
    /// <typeparam name="TInput">Type of the input value.</typeparam>
    /// <typeparam name="TResult">Type of the result from the lambda.</typeparam>
    /// <param name="input">Input value to check for null.</param>
    /// <param name="lambda">Function to apply the input value to if it is not null.</param>
    public static TResult? NullOr<TInput, TResult>(this TInput input, FuncStruct<TInput, TResult> lambda) where TResult : struct
    {
        return input == null ? null : lambda(input).Nullable();
    }
}

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

... และ 'การตั้งชื่อ' เป็นปัญหาที่แท้จริงของปัญหานี้ - ดูเหมือนไม่มีอะไรที่จะอธิบายได้ถูกต้อง (หรือ 'เพิ่ม' มากเกินไป) หรือยาว - NullOr นั้นดี แต่ให้ความสำคัญกับ IMO 'null' มากเกินไป (บวกกับคุณ มี ?? ยัง) - 'ทรัพย์สิน' หรือ 'ปลอดภัย' คือสิ่งที่ฉันใช้ value.Dot (o => o.property) ?? @default อาจจะ?
NSGaga ส่วนใหญ่ไม่ใช้งาน

@NSGaga: เรากลับไปกลับมาในชื่อนี้มาระยะหนึ่งแล้ว เราพิจารณาแล้วDotแต่พบว่าไม่สามารถอธิบายได้มากเกินไป เราตัดสินNullOrว่าเป็นการแลกเปลี่ยนที่ดีระหว่างการอธิบายตัวเองและความกะทัดรัด หากคุณไม่สนใจเรื่องการตั้งชื่อเลยคุณสามารถเรียกมันได้_ตลอดเวลา หากคุณพบว่า lambdas เขียนยุ่งยากเกินไปคุณสามารถใช้ตัวอย่างข้อมูลนี้ได้ แต่โดยส่วนตัวแล้วฉันคิดว่ามันค่อนข้างง่ายพอสมควร สำหรับ? :คุณไม่สามารถใช้กับนิพจน์ที่ซับซ้อนกว่านี้ได้คุณต้องย้ายไปยังโลคัลใหม่ NullOrช่วยให้คุณหลีกเลี่ยงสิ่งนั้นได้
Timwi

6

สิ่งที่ฉันต้องการคือการใช้เซฟคาสต์กับสตริงในกรณีที่วัตถุที่เก็บด้วยคีย์ไม่ใช่วัตถุ การใช้ToString()อาจไม่ได้ผลลัพธ์ที่คุณต้องการ

var y = Session["key"] as string ?? "none";

ดังที่ @Jon Skeet กล่าวถ้าคุณพบว่าตัวเองใช้วิธีนี้เป็นจำนวนมากหรือดีกว่า แต่อาจเป็นวิธีการขยายร่วมกับคลาส SessionWrapper ที่พิมพ์อย่างรุนแรง แม้ว่าจะไม่มีวิธีการขยาย แต่ Wrapper ที่พิมพ์แน่นหนาก็อาจเป็นความคิดที่ดี

public class SessionWrapper
{
    private HttpSessionBase Session { get; set; }

    public SessionWrapper( HttpSessionBase session )
    {
        Session = session;
    }

    public SessionWrapper() : this( HttpContext.Current.Session ) { }

    public string Key
    {
         get { return Session["key"] as string ?? "none";
    }

    public int MaxAllowed
    {
         get { return Session["maxAllowed"] as int? ?? 10 }
    }
}

ใช้เป็น

 var session = new SessionWrapper(Session);

 string key = session.Key;
 int maxAllowed = session.maxAllowed;

3

สร้างฟังก์ชันเสริม

public static String GetValue( string key, string default )
{
    if ( Session[ key ] == null ) { return default; }
    return Session[ key ].toString();
}


string y = GetValue( 'key', 'none' );

2

คำตอบของ Skeet นั้นดีที่สุด - โดยเฉพาะฉันคิดว่าเขา ToStringOrNull()ค่อนข้างสง่างามและเหมาะกับความต้องการของคุณมากที่สุด ฉันต้องการเพิ่มอีกหนึ่งตัวเลือกในรายการวิธีการขยาย:

ส่งคืนวัตถุดั้งเดิมหรือค่าสตริงเริ่มต้นสำหรับnull :

// Method:
public static object OrNullAsString(this object input, string defaultValue)
{
    if (defaultValue == null)
        throw new ArgumentNullException("defaultValue");
    return input == null ? defaultValue : input;
}

// Example:
var y = Session["key"].OrNullAsString("defaultValue");

ใช้varสำหรับค่าที่ส่งคืนเนื่องจากจะกลับมาเป็นประเภทอินพุตดั้งเดิมเป็นสตริงเริ่มต้นเมื่อnull


เหตุใดจึงมีข้อยกเว้นnull defaultValueหากไม่จำเป็น (นั่นคือinput != null)?
Attila

input != nullEVAL จะกลับวัตถุที่เป็นตัวเอง input == nullส่งคืนสตริงที่ระบุเป็นพารามิเตอร์ ดังนั้นจึงเป็นไปได้ที่คน ๆ หนึ่งสามารถโทรหาได้.OnNullAsString(null)แต่จุดประสงค์ (แม้ว่าวิธีการขยายที่ไม่ค่อยมีประโยชน์) ก็เพื่อให้แน่ใจว่าคุณได้รับวัตถุกลับมาหรือสตริงเริ่มต้น ... ไม่เป็นโมฆะ
one.beat.consumer

input!=nullสถานการณ์จะกลับอินพุตถ้าdefaultValue!=nullยังถือ; มิฉะนั้นจะโยนArgumentNullExceptionไฟล์.
Attila

0

นี่คือ "ตัวดำเนินการเอลวิส" ที่ปลอดภัยสำหรับรุ่นของ. NET ที่ไม่รองรับ?

public class IsNull
{
    public static O Substitute<I,O>(I obj, Func<I,O> fn, O nullValue=default(O))
    {
        if (obj == null)
            return nullValue;
        else
            return fn(obj);
    }
}

อาร์กิวเมนต์แรกคือวัตถุที่ทดสอบ ประการที่สองคือฟังก์ชัน และที่สามคือค่า null ดังนั้นสำหรับกรณีของคุณ:

IsNull.Substitute(Session["key"],s=>s.ToString(),"none");

มันมีประโยชน์มากสำหรับประเภทที่เป็นโมฆะด้วย ตัวอย่างเช่น:

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