检查空值的正确方法是啥?

Posted

技术标签:

【中文标题】检查空值的正确方法是啥?【英文标题】:What is the proper way to check for null values?检查空值的正确方法是什么? 【发布时间】:2012-04-05 00:26:24 【问题描述】:

我喜欢 null-coalescing 运算符,因为它可以很容易地为可空类型分配默认值。

 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"] 两次;一次用于检查,再次用于分配。有什么想法吗?

【问题讨论】:

这是我希望 C# 有一个像 Groovy has 这样的“安全导航运算符”(.?)的时候。 @Cameron:这是我希望 C# 可以将可空类型(包括引用类型)视为 monad 的时候,因此您不需要“安全导航运算符”。 空引用的发明者称其为“十亿美元的错误”,我倾向于同意。见infoq.com/presentations/… 他的实际错误是可空和非空标签类型的不安全(非语言强制)混合。 【参考方案1】:

这是我的小型类型安全“Elvis 运算符”,适用于不支持 ? 的 .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);
....

【讨论】:

【参考方案2】:

您也可以使用as,如果转换失败,则生成null

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

即使有人在Session["key"] 中填入int,这也会返回"none"

【讨论】:

这仅在您首先不需要ToString() 时才有效。 我很惊讶还没有人反对这个答案。这在语义上与 OP 想要做的完全不同。 @Timwi:OP 使用ToString() 将包含字符串的对象转换为字符串。您可以对obj as string(string)obj 执行相同的操作。这是 ASP.NET 中相当常见的情况。 @Andomar:不,OP 在一个他没有提到类型的对象(即Session["key"])上调用ToString()。它可以是任何类型的对象,不一定是字符串。【参考方案3】:

我们使用一种叫做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.NullOrTInput,TResult(TInput,FuncStructTInput,TResult)"/>
/// work without ***ing with <see cref="ObjectExtensions.NullOrTInput,TResult(TInput,FuncClassTInput,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.NullOrTInput,TResult(TInput,FuncClassTInput,TResult)"/>
/// work without ***ing with <see cref="ObjectExtensions.NullOrTInput,TResult(TInput,FuncStructTInput,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 很好,但过于强调在“空”IMO(加上你还有??仍然)-“财产”或“安全”是我使用的。 value.Dot(o=>o.property) ?? @default 也许? @NSGaga:我们在名称上来回讨论了很长一段时间。我们确实考虑过Dot,但发现它太难以描述了。我们选择NullOr 作为自我解释和简洁之间的一个很好的权衡。如果你真的根本不在乎命名,你可以一直叫它_。如果你觉得 lambda 写起来太麻烦,你可以使用 sn-p,但我个人觉得这很容易。至于? :,你不能用更复杂的表达式,你必须把它们移到一个新的本地; NullOr 可以避免这种情况。【参考方案4】:

如果它始终是string,您可以转换:

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

如果有人将intSession["key"] 中的内容塞入其中,这样做的好处是可以抱怨而不是隐藏错误。 ;)

【讨论】:

【参考方案5】:

所有建议的解决方案都很好,并回答了问题;所以这只是稍微扩展一下。目前大多数答案只处理空验证和字符串类型。您可以扩展 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;

【讨论】:

您能否提供此扩展方法的使用示例? StateBag 不是处理视图状态而不是会话吗?我正在使用 ASP.NET MVC 3,所以我真的无法简单地访问视图状态。我想你想扩展HttpSessionState 这个答案需要检索值 3x 和 2 如果成功则强制转换。 (我知道这是一本字典,但初学者可能会在昂贵的方法上使用类似的做法。) T value = source[key] as T; return value ?? defaultValue; @jberger 无法使用“as”转换为值,因为泛型类型没有类约束,因为您可能希望返回诸如 bool 之类的值。 @AlexFord 抱歉,您可能希望延长会议的 HttpSessionState。 :) 确实如此。正如理查德所指出的,需要约束。 (......如果你想使用值类型,还有另一种方法)【参考方案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;

【讨论】:

【参考方案7】:

如果你经常这样做特别是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");

【讨论】:

StackExchange 的 DataExplorer 具有与此类似的扩展方法,并具有多个默认值的额外好处。 string IsNullOrEmptyReturn(this string s, params string[] otherPossibleResults)code.google.com/p/stack-exchange-data-explorer/source/browse/… 我完全不能同意这一点。 object 上的扩展方法是诅咒和垃圾代码库,并且在 null this 值上运行而不会出错的扩展方法是纯粹的邪恶。 @NickLarsen:我说一切都要适度。与 null 一起使用的扩展方法可能非常有用,IMO - 只要它们清楚了解它们的作用。【参考方案8】:

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时作为默认字符串

【讨论】:

如果不需要nulldefaultValue,为什么要抛出异常(即input != null)? input != null eval 将返回对象本身。 input == null 返回作为参数提供的字符串。因此,一个人可以调用.OnNullAsString(null) - 但目的(尽管很少有用的扩展方法)是确保您要么取回对象,要么取回默认字符串......永远不会为空 input!=null 场景只有在defaultValue!=null 也成立时才会返回输入;否则它会抛出一个ArgumentNullException【参考方案9】:

创建辅助函数

public static String GetValue( string key, string default )

    if ( Session[ key ] == null )  return default; 
    return Session[ key ].toString();



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

【讨论】:

【参考方案10】:

怎么样

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

【讨论】:

@Matthew: 不,因为 Session 值是 Object 类型的 @BlackBear 但返回的值很可能是一个字符串,所以强制转换是有效的 这是对我的问题最直接的答案,所以我要标记答案,但 Jon Skeet 的扩展方法 .ToStringOrDefault() 是我首选的方法。但是,我在 Jon 的扩展方法中使用了这个答案 ;) 我不喜欢这个,因为如果你在会话中塞入了任何其他类型的对象,而不是你期望的,你可能会在你的程序中隐藏一些微妙的错误。我宁愿使用安全演员表,因为我认为它可能会更快地显示错误。它还避免了对字符串对象调用 ToString()。 @tvanfosson:我不确定我是否理解。你能举个例子吗?

以上是关于检查空值的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 中检查空值的最佳方法是啥?

处理空值的最优雅方法是啥

在pyspark中将值随机更改为空值的最有效方法是啥?

检查数据表中是不是包含空值的最佳方法

SqlDataReader 检查空值的最佳方法 -sqlDataReader.IsDBNull vs DBNull.Value

在 ms 访问中排除空值的选择查询是啥