检查空值的正确方法是什么?


122

我喜欢null-coalescing运算符,因为它使为可为null的类型分配默认值变得容易。

 int y = x ?? -1;

很好,除非我需要使用做一些简单的事情x。例如,如果我想检查Session,那么我通常最终不得不写一些更冗长的东西。

我希望我可以这样做:

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

但是您不能,因为在.ToString()null检查之前调用gets,所以如果Session["key"]为null 则失败。我最终这样做:

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一样拥有一个“安全导航运算符”()的时候。
卡梅伦

2
@Cameron:这是我希望C#可以将可空类型(包括引用类型)视为monad的时候,因此您不需要“安全导航运算符”。
乔恩·普迪

3
空引用的发明者称其为“十亿美元的错误”,我倾向于同意。参见infoq.com/presentations/…–
Jamie Ide

他的实际错误是可空类型和非nullabel类型的不安全混合(不是由语言强制执行的)。
MSalters 2012年

@JamieIde感谢您的非常有趣的链接。:)
BobRodes 2015年

Answers:


182

关于什么

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

79
这一力量很强大。
2012年

2
@Matthew:没有,因为会话值是Object类型
BlackBear

1
@BlackBear,但返回的值很可能是字符串,因此
强制转换

这是对我的问题的最直接答案,因此我正在标记答案,但是Jon Skeet的扩展方法.ToStringOrDefault()是我首选的解决方法。但是,我在Jon的扩展方法中使用了这个答案;)
2012年

10
我不喜欢这样做,因为如果会话中塞满了其他类型的对象,则可能会在程序中隐藏一些细微的错误。我宁愿使用安全的强制类型转换,因为我认为它可能更快地显示错误。它还避免了在字符串对象上调用ToString()。
tvanfosson 2012年

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()简单而优雅。一个不错的解决方案。
2012年

7
我完全不同意这一点。扩展方法object是一种诅咒,会破坏代码库,而对空this值无错误操作的扩展方法纯属邪恶。
Nick Larsen 2012年

10
@NickLarsen:我说一切都适度。扩展方法与空的工作是非常有用的,IMO -只要他们清楚了解他们在做什么。
乔恩·斯基特

3
@ one.beat.consumer:是的。如果只是格式设置(或任何拼写错误),那将是一回事,但是更改作者对方法名称的选择超出了IMO通常的适当编辑范围。
乔恩·斯基特

6
@ one.beat.consumer:纠正语法和拼写错误时,这很好-但是更改某人(任何人,而不仅仅是我)明确选择的名字对我来说是不同的。到那时,我建议在评论中建议它。
乔恩·斯基特

21

您还可以使用asnull如果转换失败,它将产生:

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

这将返回"none"即使有人塞进一个intSession["key"]


1
这仅在您一开始不需要时才起作用ToString()
亚伯(Abel)

1
令人惊讶的是,没有人对这个答案持否定态度。从语义上讲,这与OP想要做的完全不同。
Timwi'3

@Timwi:OP用于ToString()将包含字符串的对象转换为字符串。您可以使用obj as string或进行相同的操作(string)obj。在ASP.NET中,这是相当普遍的情况。
2012年

5
@Andomar:否,OP正在调用其未提及类型ToString()的对象(即Session["key"])。它可以是任何类型的对象,不一定是字符串。
Timwi 2012年

13

如果始终为string,则可以强制转换:

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

这样的好处是抱怨而不是如果有人在里面塞了一个东西int或隐藏了错误Session["key"]。;)


10

所有建议的解决方案都是好的,并且可以回答问题;所以这只是略微扩展。当前,大多数答案仅涉及null验证和字符串类型。您可以扩展该StateBag对象以使其包含通用GetValueOrDefault方法,类似于Jon Skeet发布的答案。

一种简单的通用扩展方法,该方法接受字符串作为键,然后进行类型检查会话对象。如果对象为null或不同类型,则返回默认值,否则返回强类型的会话值。

像这样

/// <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
2012年

如果成功,此答案需要检索值3x和2强制转换。(我知道这是一本字典,但是初学者可能会在昂贵的方法上使用类似的做法。)
Jake Berger

3
T value = source[key] as T; return value ?? defaultValue;
杰克·伯杰

1
@jberger无法使用“ as”强制转换为值,因为通用类型没有类约束,因为您可能希望返回诸如bool之类的值。@AlexFord,我很抱歉,您想要扩展HttpSessionState该会话。:)
理查德(Richard)

确实。正如Richard所指出的,需要约束。(...和另一种方法,如果您想使用值类型)
Jake Berger 2012年

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

是的,这是针对所要解决问题的更通用的答案-您击败了我-并且是安全导航的候选人(如果您不介意lambda -s代表简单的事情)-但这写起来还是有点麻烦,好 :)。我个人总是选择?:而不是(如果价格不贵,则无论如何
都要

...而“命名”是这个问题的真正问题-似乎没有什么能正确描述(或“添加”太多)或太长了-NullOr很好,但过分强调“ null” IMO(加上您还有??)-我使用的是“属性”或“安全”。value.Dot(o => o.property)?? @默认也许?
NSGaga-2012年

@NSGaga:我们来回穿梭了很长时间。我们确实考虑过,Dot但发现它太缺乏描述性。我们选择NullOr在自我解释和简洁之间进行权衡取舍。如果您真的根本不在乎命名,那么可以随时称之为_。如果您发现lambda太笨拙而无法编写,则可以为此使用一个代码段,但就我个人而言,我觉得它很容易。至于? :,您不能将其用于更复杂的表达式,而必须将它们移至新的本地。NullOr让您避免这种情况。
Timwi'3

6

对于一个,我的首选是使用安全的强制转换来字符串化,以防与密钥一起存储的对象不是一个。使用ToString()可能无法获得所需的结果。

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

正如@Jon Skeet所说,如果您发现自己经常这样做,则可以使用扩展方法,或者更好的方法是将扩展方法与强类型的SessionWrapper类结合使用。即使没有扩展方法,强类型包装也可能是一个好主意。

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 2012年

input != nullEVAL将返回对象本身。input == null返回作为参数提供的字符串。因此可能有人可以打电话给我.OnNullAsString(null)-但目的(尽管很少有用的扩展方法)是确保您可以拿回对象或默认字符串...永远不会为空
one.beat.consumer 2012年

input!=null方案仅在defaultValue!=null也成立的情况下才返回输入;否则会抛出异常ArgumentNullException
Attila 2012年

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

第一个参数是被测试的对象。其次是功能。第三是空值。因此,对于您的情况:

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

对于可空类型也非常有用。例如:

decimal? v;
...
IsNull.Substitute(v,v.Value,0);
....
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.