Cách thích hợp để kiểm tra giá trị null là gì?


122

Tôi yêu toán tử hợp nhất null vì nó giúp dễ dàng gán giá trị mặc định cho các loại nullable.

 int y = x ?? -1;

Điều đó thật tuyệt, trừ khi tôi cần làm một cái gì đó đơn giản x. Ví dụ, nếu tôi muốn kiểm tra Session, thì cuối cùng tôi thường phải viết một cái gì đó dài dòng hơn.

Tôi ước tôi có thể làm điều này:

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

Nhưng bạn không thể bởi vì .ToString()được gọi trước kiểm tra null nên nó không thành công nếu Session["key"]là null. Tôi cuối cùng làm điều này:

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

Nó hoạt động và tốt hơn, theo ý kiến ​​của tôi, hơn là sự thay thế ba dòng:

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

Mặc dù nó hoạt động nhưng tôi vẫn tò mò nếu có một cách tốt hơn. Dường như không có vấn đề gì tôi luôn phải tham khảo Session["key"]hai lần; một lần cho kiểm tra, và một lần nữa cho bài tập. Có ý kiến ​​gì không?


20
Đây là khi tôi muốn C # có "toán tử điều hướng an toàn" ( .?) như Groovy có .
Cameron

2
....
Jon Purdy

3
Người phát minh ra các tài liệu tham khảo null gọi đó là "sai lầm tỷ đô" và tôi có xu hướng đồng ý. Xem infoq.com/presentations/ từ
Jamie Ide

Sai lầm thực tế của anh ta là sự pha trộn không an toàn (không được thi hành bằng ngôn ngữ) của các loại nullable và không nullabel.
MSalters

@JamieIde Cảm ơn vì một liên kết rất thú vị. :)
BobRodes

Câu trả lời:


182

Thế còn

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

79
Với một lực lượng mạnh mẽ như này.
Chev

2
@Matthew: Không vì giá trị Phiên thuộc loại Đối tượng
BlackBear

1
@BlackBear nhưng giá trị được trả về rất có thể là một chuỗi, vì vậy, giá trị cast là hợp lệ
Firo

Đây là câu trả lời trực tiếp nhất cho câu hỏi của tôi vì vậy tôi đang đánh dấu câu trả lời, nhưng phương pháp mở rộng của Jon Skeet .ToStringOrDefault()là cách làm ưa thích của tôi. Tuy nhiên, tôi đang sử dụng câu trả lời này trong phương pháp mở rộng của Jon;)
Chev

10
Tôi không thích điều này bởi vì nếu bạn có bất kỳ loại đối tượng nào khác được nhồi trong phiên hơn những gì bạn mong đợi, bạn có thể đang ẩn một số lỗi tinh vi trong chương trình của mình. Tôi thà sử dụng dàn diễn viên an toàn vì tôi nghĩ rằng nó có khả năng xuất hiện lỗi nhanh hơn. Nó cũng tránh gọi ToString () trên một đối tượng chuỗi.
tvanfosson

130

Nếu bạn thường xuyên làm điều này cụ thể vớiToString() thì bạn có thể viết một phương thức mở rộng:

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

...

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

Hoặc một phương thức lấy mặc định, tất nhiên:

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

...

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

16
.ToStringOrDefault()là đơn giản và thanh lịch. Một giải pháp tốt đẹp.
Chev

7
Tôi không thể đồng ý với điều này cả. Các phương thức mở rộng trên objectlà một lời nguyền và làm hỏng cơ sở mã và các phương thức mở rộng hoạt động mà không có lỗi trên thiscác giá trị null là tà ác.
Nick Larsen

10
@NickLarsen: Mọi thứ trong chừng mực, tôi nói. Các phương thức mở rộng hoạt động với null có thể rất hữu ích, IMO - miễn là chúng rõ ràng về những gì chúng làm.
Jon Skeet

3
@ one.beat.consumer: Yup. Nếu đó chỉ là định dạng (hoặc bất kỳ lỗi chính tả nào), đó sẽ là một điều, nhưng việc thay đổi tên phương thức của tác giả là vượt quá những gì chỉnh sửa thông thường, IMO.
Jon Skeet

6
@ one.beat.consumer: Khi sửa lỗi ngữ pháp và lỗi chính tả, điều đó tốt - nhưng việc thay đổi tên mà ai đó (bất kỳ ai, không chỉ tôi) rõ ràng đã cố tình chọn khác với tôi. Tại thời điểm đó, tôi muốn đề nghị nó trong một bình luận thay thế.
Jon Skeet

21

Bạn cũng có thể sử dụng as, sẽ mang lại kết quả nullnếu chuyển đổi không thành công:

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

Điều này sẽ trở lại "none"ngay cả khi ai đó nhét intvào Session["key"].


1
Điều này chỉ hoạt động khi bạn không cần ToString()ở nơi đầu tiên.
Abel

1
Tôi ngạc nhiên không ai đánh giá thấp câu trả lời này. Điều này hoàn toàn khác về mặt ngữ nghĩa so với những gì OP muốn làm.
Timwi

@Timwi: OP sử dụng ToString()để chuyển đổi một đối tượng có chứa chuỗi thành chuỗi. Bạn có thể làm tương tự với obj as stringhoặc (string)obj. Đây là một tình huống khá phổ biến trong ASP.NET.
Andomar

5
@Andomar: Không, OP đang kêu gọi ToString()một đối tượng (cụ thể là Session["key"]) mà anh ấy không đề cập đến. Nó có thể là bất kỳ loại đối tượng nào, không nhất thiết phải là một chuỗi.
Timwi

13

Nếu nó sẽ luôn là một string, bạn có thể chọn:

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

Điều này có lợi thế là phàn nàn thay vì che giấu lỗi lầm nếu ai đó nhét một intcái gì đó vào Session["key"]. ;)


10

Tất cả các giải pháp được đề xuất là tốt, và trả lời câu hỏi; Vì vậy, đây chỉ là để mở rộng trên nó một chút. Hiện tại, phần lớn các câu trả lời chỉ liên quan đến xác thực null và các loại chuỗi. Bạn có thể mở rộng StateBagđối tượng để bao gồm một GetValueOrDefaultphương thức chung , tương tự như câu trả lời được đăng bởi Jon Skeet.

Một phương thức mở rộng chung đơn giản chấp nhận một chuỗi làm khóa và sau đó gõ kiểm tra đối tượng phiên. Nếu đối tượng là null hoặc không cùng loại, mặc định được trả về, nếu không, giá trị phiên được trả về được gõ mạnh.

Một cái gì đó như thế này

/// <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
Bạn có thể bao gồm một mẫu sử dụng cho phương pháp mở rộng này? Không StateBag đối phó với trạng thái xem và không phiên? Tôi đang sử dụng ASP.NET MVC 3 vì vậy tôi không thực sự có quyền truy cập đơn giản để xem trạng thái. Tôi nghĩ rằng bạn muốn mở rộng HttpSessionState.
Chev

câu trả lời này yêu cầu truy xuất giá trị 3x và 2 phôi nếu thành công. (tôi biết đó là một cuốn từ điển, nhưng những người mới bắt đầu có thể sử dụng các thực tiễn tương tự trên các phương pháp đắt tiền.)
Jake Berger

3
T value = source[key] as T; return value ?? defaultValue;
Jake Berger

1
@jberger Việc chuyển sang giá trị bằng cách sử dụng "as" là không thể truy cập do không có ràng buộc lớp đối với loại chung vì có khả năng bạn có thể muốn trả về một giá trị như bool. @AlexFord Xin lỗi, bạn muốn gia hạn HttpSessionStatephiên. :)
Richard

thật. như Richard lưu ý, đòi hỏi sự ràng buộc. (... Và một phương pháp khác nếu bạn muốn sử dụng các loại giá trị)
Jake Berger

7

Chúng tôi sử dụng một phương pháp gọi là NullOr.

Sử dụng

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

Nguồn

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

Vâng, đây là câu trả lời chung chung hơn cho vấn đề dự định - bạn đánh bại tôi - và một ứng cử viên cho điều hướng an toàn (nếu bạn không bận tâm đến lambda-s cho những điều đơn giản) - nhưng vẫn hơi khó viết, tốt :). Cá nhân tôi luôn luôn chọn? : thay vào đó (nếu không đắt, nếu nó được sắp xếp lại bằng mọi cách) ...
NSGaga hầu hết không hoạt động vào

... Và 'Đặt tên' là vấn đề thực sự với vấn đề này - dường như không có gì thực sự miêu tả đúng (hoặc 'thêm' quá nhiều), hoặc dài - NullOr là tốt nhưng quá chú trọng vào IMO 'null' (cộng với bạn có ?? vẫn) - 'Tài sản' hoặc 'An toàn' là những gì tôi đã sử dụng. value.Dot (o => o.property) ?? @default có lẽ?
NSGaga-hầu như không hoạt động

@NSGaga: Chúng tôi đã qua lại trên tên khá lâu. Chúng tôi đã xem xét Dotnhưng thấy nó quá không mô tả. Chúng tôi giải quyết NullOrnhư một sự đánh đổi tốt giữa tự giải thích và ngắn gọn. Nếu bạn thực sự không quan tâm đến việc đặt tên, bạn luôn có thể gọi nó _. Nếu bạn thấy lambdas quá cồng kềnh để viết, bạn có thể sử dụng một đoạn trích cho việc này, nhưng cá nhân tôi thấy nó khá dễ dàng. Đối với ? :, bạn không thể sử dụng điều đó với các biểu thức phức tạp hơn, bạn phải chuyển chúng sang một địa phương mới;NullOrcho phép bạn tránh điều đó
Timwi

6

Sở thích của tôi, đối với một lần tắt, sẽ là sử dụng một chuỗi an toàn để xâu chuỗi trong trường hợp đối tượng được lưu trữ với khóa không phải là một. Sử dụng ToString()có thể không có kết quả bạn muốn.

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

Như @Jon Skeet nói, nếu bạn thấy mình làm điều này rất nhiều phương thức mở rộng hoặc tốt hơn, có thể là một phương thức mở rộng kết hợp với lớp SessionWrapper được gõ mạnh. Ngay cả khi không có phương thức mở rộng, trình bao bọc được gõ mạnh có thể là một ý tưởng tốt.

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

Được dùng như

 var session = new SessionWrapper(Session);

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

3

tạo một chức năng phụ trợ

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


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

2

Câu trả lời của Skeet là tốt nhất - đặc biệt tôi nghĩ rằng anh ấy ToStringOrNull()khá thanh lịch và phù hợp với nhu cầu của bạn nhất. Tôi muốn thêm một tùy chọn nữa vào danh sách các phương thức mở rộng:

Trả về đối tượng gốc hoặc giá trị chuỗi mặc định cho 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");

Sử dụng varcho giá trị được trả về vì nó sẽ trở lại dưới dạng loại của đầu vào ban đầu, chỉ là chuỗi mặc định khinull


Tại sao ném một ngoại lệ vào null defaultValuenếu nó không cần thiết (đó là input != null)?
Attila

Một input != nulleval sẽ trả lại đối tượng như chính nó. input == nulltrả về một chuỗi được cung cấp dưới dạng param. do đó, một người có thể gọi .OnNullAsString(null)- nhưng mục đích (mặc dù hiếm khi là phương thức mở rộng hữu ích) là để đảm bảo bạn lấy lại đối tượng hoặc chuỗi mặc định ... không bao giờ null
one.beat.consumer 21/03

Các input!=nullkịch bản sẽ chỉ trả lại các đầu vào nếu defaultValue!=nullcũng nắm giữ; nếu không nó sẽ ném một ArgumentNullException.
Attila

0

Đây là loại "toán tử Elvis" an toàn nhỏ của tôi cho các phiên bản .NET không hỗ trợ?

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

Đối số đầu tiên là đối tượng được thử nghiệm. Thứ hai là chức năng. Và thứ ba là giá trị null. Vì vậy, đối với trường hợp của bạn:

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

Nó rất hữu ích cho các loại nullable quá. Ví dụ:

decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
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.